- 后端
- 人工智能
- 模型推理服务
- 集群管理
- 可观测性
【免费下载链接】gpustack
A GPU cluster manager for high-performance AI model serving (vLLM, SGLang) and on-demand SSH-accessible GPU instances.
GPUStack 的调度器(Scheduler)是模型部署的核心决策引擎:它负责为新模型实例选择最优的 Worker/GPU 放置位置(扩容),并在副本缩减时决定优先删除哪些现有实例(缩容)。本文基于官方调度器文档 docs/scheduler.md,并结合仓库源码(gpustack/scheduler/、gpustack/policies/)深入讲解其"过滤 + 评分"的两阶段机制、六段 Worker 过滤链、按后端差异化的资源候选评估,以及扩容/缩容两套评分链的完整实现。读完本文,你将掌握 GPUStack 调度决策的完整链路,并学会通过环境变量调整调度策略。
调度器概览:两大职责与两阶段模型
调度器承担两类相互关联的任务:
- 扩容(Scale Up):为新建的模型实例挑选最佳的 Worker/GPU 放置位置;
- 缩容(Scale Down):为现有模型实例排序,当副本数减少时优先移除最不偏好的实例。
无论哪个方向,调度器都遵循同样简洁的两阶段流程:
- 过滤(Filter):找出真正能运行该模型的候选者,相当于构建候选清单;
- 评分(Score):为每个候选者打分,然后挑选最适合当前目标的一个。
从源码看,调度器的主体是 gpustack/scheduler/scheduler.py 中的Scheduler类。它通过AsyncUniqueQueue异步队列消费待调度实例,start()时以_check_interval = 180秒为周期注册IntervalTrigger定时任务做全量扫描,同时订阅ModelInstance的CREATED事件做增量触发,两者互为兜底。模型实例的状态在调度过程中会依次经历PENDING → ANALYZING → SCHEDULED的流转(_evaluate中先将实例置为ANALYZING并写入 "Evaluating resource requirements",找到候选后由apply_candidate_to_instance置为SCHEDULED并落盘 worker/gpu 信息),最终由 Worker 侧上报进入RUNNING。
扩容调度(Scale-Up)
1. 基础 Worker 过滤链
调度器首先对 Worker 列表做一轮基础过滤,这一步主要处理非资源类约束:集群、标签、后端兼容性、指定 GPU 与本地路径可用性。过滤链按以下顺序执行(与源码 find_candidate 中filters列表的构建顺序一一对应):
- Cluster Filter(集群过滤):只保留目标集群内的 Worker;
- GPU Matching Filter(GPU 匹配过滤):当模型显式指定 GPU 时,收窄 Worker 集合;
- Label Matching Filter(标签匹配过滤):只保留标签满足模型标签选择器的 Worker;
- Status Filter(状态过滤):只保留
READY状态的 Worker(status_filter.py 中逐 worker 比对WorkerStateEnum.READY); - Backend Framework Filter(后端框架过滤):剔除加速器或运行时能力与所选后端不匹配的 Worker;
- Local Path Filter(本地路径过滤):仅针对显式指定 GPU 的
LOCAL_PATH模型,移除配置的模型路径不存在的 Worker。
所有过滤器的抽象基类与链式执行逻辑定义在 policies/base.py:WorkerFilterChain.filter()按顺序逐个应用过滤器,一旦某轮过滤后 Worker 列表为空则提前终止,并聚合所有过滤原因生成诊断消息。只有通过这轮基础过滤的 Worker 才会进入基于资源的候选过滤。
注意:在 find_candidate 中,基础过滤链还会根据情况追加
PDModeRuntimeFilter(PD 解耦运行时过滤)以及可选的GatherFloorFilter(MUST_GATHER聚集策略的兜底过滤),这些属于多角色/PD 部署场景的扩展,普通单角色模型不受影响。
2. 基于资源的候选过滤
这一阶段同样是过滤,但过滤依据从元数据与 Worker 状态换成了资源本身:该 Worker 或放置方案能否提供足够的 RAM/VRAM 来运行模型?
资源需求的估算方式取决于模型类型:
- GGUF 模型:使用 GGUF 解析器(gguf-parser)估算资源需求;
- 其他模型类型:由对应后端估算,例如 vLLM、SGLang、MindIE、VoxBox 等。
后端能力不同,可用的降级路径也不同:
- vLLM、SGLang、MindIE:主要使用基于 GPU 的放置方案,此处不使用纯 CPU 或部分 offload 的降级路径;
- GGUF、自定义后端(Custom)、VoxBox:使用 GGUF 解析器估算资源需求,支持 GPU offload、部分 offload 或 CPU 执行;
- 自定义后端、VoxBox:支持 GPU offload 或 CPU 执行。
候选者按顺序评估,一旦某个策略返回可运行的候选者即停止。总体上调度器依次尝试:
- 单 Worker 单 GPU:一个 Worker 上的一块 GPU 足以满足模型需求;
- 单 Worker 多 GPU:单块 GPU 不够时,同一 Worker 上的多块 GPU 联合使用;
- 跨 Worker 分布式推理:后端支持分布式执行时,可使用多个 Worker 上的 GPU;
- 部分 offload 或 CPU 执行:仅当该模型类型与后端支持这些降级模式时使用。
这里有几个关键细节:
- 资源适配过滤在得到候选的第一个策略处停止;
- 分布式候选可以包含主 Worker 之外的从属 Worker(
subordinate_workers); - 显式 GPU 选择在基础过滤与资源适配过滤两个阶段都会被尊重。
源码层面,选择哪个资源适配 selector 由 build_candidate_selector 决定,它按优先级依次处理:纯 CPU 角色 → GPU 实例类型整卡声明(InstanceTypeWholeCardSelector)→ vGPU 切分(VGPUResourceFitSelector)→ GGUF 模型(GGUFResourceFitSelector)→ Ascend MindIE → vLLM(Omni 模型除外)→ SGLang → 其余走CustomBackendResourceFitSelector。GGUF 的评估逻辑集中在 gguf_resource_fit_selector.py,其中定义了Single-Worker Single-GPU Full Offloading、Single-Worker Multi-GPU Full Offloading、Distributed Deployment、Single-Worker Partial Offloading、CPU Offloading等事件动作,与文档描述的尝试顺序完全吻合。资源估算过程依赖 scheduler/calculator.py 的calculate_gguf_model_resource_claim(GGUF)与get_pretrained_config_with_workers(其他模型,读取预训练配置),调度器在_evaluate中还会据此自动补全模型的categories(LLM/EMBEDDING/RERANKER/IMAGE)、distributable与gpus_per_replica等字段。
3. 候选评分
找到可运行的候选后,调度器用一条评分链(scorer chain)给候选打分,并选择总分最高的候选。当前扩容评分链为:
- Placement Scorer(放置评分器)
- Model File Locality Scorer(模型文件本地性评分器)
候选总分是所有启用评分器得分之和。评分链的实现见 score_chain.py 的CandidateScoreChain:每个评分器独立计算得分后累加(total_scores[id(candidate)] += candidate.score),最终按总分排序。
Placement Scorer(放置评分器)
放置评分器在扩容时始终启用(源码见 placement_scorer.py),它根据模型的placement_strategy字段选择 binpack 或 spread 策略:
- Binpack(装箱):目标是把尽可能多的模型实例"打包"进最少的"箱子"(如 Worker/GPU),在不超过容量上限的前提下最大化资源利用率、最小化箱子数量。模型实例被放到剩余空间最少的箱子中,以最小化每个箱子的剩余容量。
- Spread(分散):目标是把多个模型实例尽可能均匀地分布到不同 Worker 上,提升系统容错性与负载均衡能力。
额外行为:
- 对于 GPU 放置,VRAM 压力比 RAM 压力权重更重。源码中
ResourceWeight的默认值为vram = 2.0、ram = 1.0,即 VRAM 占用率对得分的贡献是 RAM 的两倍; - 对于纯 CPU 放置,只考虑 RAM 利用率。
从源码结构看,spread 策略的得分还会区分"当前模型的实例数(current)"与"其他模型的实例数(others)",并按 Worker(权重 0.85)与 GPU(权重 0.15)两个维度加权计算,优先选择当前模型实例数最少的 Worker/GPU。
Model File Locality Scorer(模型文件本地性评分器)
该评分器用于把放置偏向于已经拥有所需模型文件且文件处于READY状态的 Worker(源码见 model_file_locality_scorer.py)。其行为:
- 查询已就绪主模型文件的 Worker;
- 若配置了投机解码(speculative decoding),同时查询草稿模型(draft model)的就绪文件;
- 按"参与该候选的 Worker 中已拥有这些文件的比例"给每个候选打分;
- 主模型本地性的权重高于草稿模型本地性。源码中主模型比例权重为 1.0,草稿模型比例权重为 0.5,最终得分为
(main_ratio + 0.5 * draft_ratio) / 1.5 * max_score。
需要说明的是,该评分器在得分上限max_score <= 0时直接跳过。默认上限由环境变量GPUSTACK_SCHEDULER_SCALE_UP_LOCALITY_MAX_SCORE控制(默认 5),设置为 0 可关闭本地性偏好。
另外,从 find_candidate 的代码可以看到,当集群声明了拓扑(topology)且实例属于多角色分组时,评分链还会追加TopologyProximityScorer(拓扑邻近评分器)与PairingAffinityScorer(配对亲和评分器),前者偏向靠近同组其他角色的 Worker,后者偏向与对向角色同机的 Worker——这些是 PD 解耦部署(prefill/decode 分离)场景的专用扩展,普通模型不受影响。
4. 最终选择
评分完成后,调度器选择总分最高的候选,将模型实例指派到该放置位置。pick_highest_score_candidate 采用简单的线性扫描比较各候选score字段;选中后 apply_candidate_to_instance 会把 worker_id、worker_name、gpu_indexes、gpu_addresses、computed_resource_claim、分布式从属 Worker 等十个字段写入实例行,状态置为SCHEDULED,并针对 vLLM/MindIE/SGLang 设置distributed_servers.mode = INITIALIZE_LATER。
缩容调度(Scale-Down)
当期望副本数低于现有模型实例数时,控制器会对现有实例排序,先删除排名最低的实例。入口在 gpustack/server/controllers.py 的find_scale_down_candidates:它调用ModelInstanceScoreChain对实例评分后,按分数升序排列(sorted(..., reverse=False)),调用方从列表头部开始取len(candidates) - replicas个实例执行删除。
缩容使用独立的评分链,作用于现有模型实例:
- Status Scorer(状态评分器)
- Offload Layer Scorer(Offload 层数评分器)
- Placement Scorer(放置评分器)
注意缩容评分链与扩容不同:它使用ModelInstanceScoreChain而非CandidateScoreChain,后者会对各评分器的分数按总分上限归一化(scale_factor = total_max_score / sum_max_score)。
Status Scorer(状态评分器)
状态评分器偏好健康副本,使不健康或尚未就绪的副本成为优先删除对象。源码 status_scorer.py 的打分规则为:
- Worker 处于
NOT_READY或实例处于ERROR:得 0 分; - Worker
READY且实例RUNNING:得满分(max_score,默认 100); - 其他中间状态:得满分的一半。
分数越低越先被删除,因此不健康、报错、未就绪的实例会首先成为缩容目标。
Offload Layer Scorer(Offload 层数评分器)
对于上报了total_layers与offload_layers的 GGUF 模型,该评分器偏好已 offload 更多层的实例(源码见 offload_layer_scorer.py):
- 完全 offload(
total_layers == offload_layers):得满分; - 部分 offload:按
offload_layers / total_layers比例得分; - 无 offload 层元数据:得 0 分。
其语义是:已把更多层驻留在 GPU 上的实例"价值更高",缩容时应保留;offload 层数少(更依赖 CPU)的实例先删。
Placement Scorer 的缩容模式
同一个放置评分器在缩容时被复用,但它从"移除视角"而非"放置视角"评估现有放置(placement_scorer.py 中通过scale_type=ScaleTypeEnum.SCALE_DOWN切换)。放置仍反映模型的binpack或spread策略,但分数被解释为"保留偏好":
- binpack 缩容模式下,单 GPU 打分把自身 claim 计入可分配资源(
allocatable.vram + vram_claim),即评估"若删掉该实例会释放多少资源/剩余密度"; - spread 缩容模式同样基于实例分布计数评估。
因此放置分数越低的实例越可能被优先移除。默认的缩容放置分上限仅为 1(GPUSTACK_SCHEDULER_SCALE_DOWN_PLACEMENT_MAX_SCORE),远低于状态评分器的 100,说明缩容决策以实例健康状态为主、放置策略为辅。此外,若实例属于多角色分组,缩容链还会追加PairingRetentionScorer,保留与对向角色共置的实例。
调度策略相关环境变量
调度器各评分器的得分上限(即各策略的相对权重)可通过环境变量调整,默认值定义在 gpustack/envs/init.py:
| 环境变量 | 默认值 | 作用 |
|---|---|---|
GPUSTACK_SCHEDULER_SCALE_UP_PLACEMENT_MAX_SCORE | 100 | 扩容放置评分器得分上限 |
GPUSTACK_SCHEDULER_SCALE_UP_LOCALITY_MAX_SCORE | 5 | 扩容模型文件本地性评分器得分上限(设为 0 关闭) |
GPUSTACK_SCHEDULER_SCALE_DOWN_STATUS_MAX_SCORE | 100 | 缩容状态评分器得分上限 |
GPUSTACK_SCHEDULER_SCALE_DOWN_OFFLOAD_MAX_SCORE | 10 | 缩容 offload 层数评分器得分上限 |
GPUSTACK_SCHEDULER_SCALE_DOWN_PLACEMENT_MAX_SCORE | 1 | 缩容放置评分器得分上限 |
GPUSTACK_SCHEDULER_SCALE_DOWN_PAIRING_MAX_SCORE | 20 | 缩容配对保留评分器得分上限 |
GPUSTACK_SCHEDULER_PAIRING_AFFINITY_MAX_SCORE | 200 | 扩容配对亲和评分器得分上限(设为 0 关闭) |
GPUSTACK_SCHEDULER_TOPOLOGY_PROXIMITY_MAX_SCORE | 150 | 扩容拓扑邻近评分器得分上限 |
GPUSTACK_SCHEDULER_GROUP_PAIR_LOCALITY_WEIGHT | 1.0 | 分组配对本地性权重 |
GPUSTACK_SCHEDULER_GROUP_FILE_LOCALITY_WEIGHT | 0.3 | 分组文件本地性权重 |
从默认值可以看出 GPUStack 的调优思路:扩容时放置策略(100)是主导信号,文件本地性(5)是轻量级的加速偏好;缩容时实例健康状态(100)主导、offload 程度(10)其次、放置策略(1)仅作微调。这些权重均可按集群特征(如是否频繁冷启动下载模型、是否存在异构 GPU)进行调整。
代码地图:从文档到实现
如果希望进一步研读调度器实现,仓库中以下位置与本文内容直接对应:
- gpustack/scheduler/scheduler.py:
Scheduler主循环、find_candidate过滤与评分编排、build_candidate_selector、apply_candidate_to_instance; - gpustack/scheduler/calculator.py:GGUF 与非 GGUF 模型的资源 claim 估算;
- gpustack/policies/base.py:
WorkerFilter/WorkerFilterChain、ScheduleCandidatesScorer/ModelInstanceScorer等抽象基类; - gpustack/policies/worker_filters/:六段基础过滤链的逐一实现;
- gpustack/policies/candidate_selectors/:按后端分派的资源适配选择器;
- gpustack/policies/scorers/:placement、model file locality、status、offload layer 等评分器与评分链;
- gpustack/server/controllers.py:
find_scale_down_candidates缩容评分入口; - tests/scheduler/ 与 tests/policies/:调度器与各过滤/评分策略的测试用例,是理解预期行为的可靠参考。
小结
GPUStack 的调度器以"过滤 + 评分"两阶段模型统一了扩容与缩容两种场景:扩容时通过六段基础过滤链收缩 Worker 集合,再按模型类型与后端能力进行资源适配评估,最后以放置策略(binpack/spread)为主、模型文件本地性为辅完成打分选优;缩容时则以实例健康状态为首要依据,结合 GGUF offload 层数与放置策略排序,升序删除最低分实例。理解这条决策链,是排查"实例长时间 PENDING"、优化异构集群资源利用率、以及调整模型分布策略的前提。
- 后端
- 人工智能
- 模型推理服务
- 集群管理
- 可观测性
【免费下载链接】gpustack
A GPU cluster manager for high-performance AI model serving (vLLM, SGLang) and on-demand SSH-accessible GPU instances.
相关推荐
Volcano Job 弹性扩缩容:基于 JobUpdatedEvent 与插件机制的 Scale Up/Down 设计解析
Volcano Job 弹性扩缩容:基于 JobUpdatedEvent 与插件机制的 Scale Up/Down 设计解析 本文以 Volcano 设计文档
云原生后端任务调度批处理ERNIE-Image-Turbo性能优化:如何在消费级GPU上高效运行
ERNIE Image Turbo性能优化:如何在消费级GPU上高效运行 ERNIE Image Turbo是百度ERNIE Image团队开发的开源文本到图像
rkt容器调度扩展:调度器扩展点与插件开发实例
rkt容器调度扩展:调度器扩展点与插件开发实例 一、调度器扩展背景与核心价值 在容器编排领域,调度策略的灵活性直接影响资源利用率与业务稳定性。rkt作为遵循Ap
容器运行时云原生网络
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考