Seldon Core 2 性能调优方法论:从模型负载测试到 Pipeline 数据链路的系统性实践
Seldon Core 2 性能调优方法论:从模型负载测试到 Pipeline 数据链路的系统性实践
在 MLOps 生产环境中,系统性能可以定义为一个 ML 模型或应用在真实负载下运行的效率与有效性,通常通过延迟(latency)、吞吐(throughput)、可扩展性(scalability)和资源效率(resource-efficiency)等维度衡量。这些因素深度耦合、互相影响,配置上的任何改动往往都意味着某种权衡。本文基于 Seldon Core 2(仓库中 scheduler、operator 等模块所实现的运行时)的性能调优文档,给出"先调优单个 Model、再调优由 Model 组成的 Pipeline"的完整方法论:读完后你将掌握如何确定单副本负载饱和点、如何为模型选择合适的 CPU/GPU 基础设施与推理执行策略,以及如何通过 Kafka 分区、组件副本与 maxShardCountMultiplier 等参数优化 Pipeline 数据链路并控制其横向扩展行为。
一、性能的定义与总体调优思路
在 Seldon Core 2 部署的系统中,性能调优可用的"杠杆"横跨多个层面:基础设施(硬件与资源规格)、模型本身(结构、精度、存储格式)、推理执行方式(协议、批处理、并行度),以及 Core 2 暴露的运行时配置。
推荐的问题分解路径是:
- 先调优 Model:理解并测试单个部署模型的基线性能,再通过基础设施、模型工件、推理执行三个方向的变更去调优;
- 再调优 Pipeline(如适用):在模型性能达标的基础上,测试由这些模型组成的复杂 Pipeline,定位关键路径并通过 Core 2 配置优化数据处理,最后理解组件随 Pipeline/模型数量扩展的行为。
无论针对模型还是流水线,在做任何调优之前都应先运行测试,建立基线性能特征。下文按这一顺序展开,各节内容对应仓库文档 docs-gb/performance-tuning/ 下的两个子章节:Model 调优 与 Pipeline 调优。
二、模型层性能调优
2.1 负载测试:先确定单副本的负载饱和点
在着手改善延迟或吞吐之前,必须先对部署到推理服务器(MLServer 或 Triton)上的模型做负载测试,理解其现有的性能特征。测试目标是搞清楚模型副本在饱和区(单副本可承受的最大吞吐)与非饱和区的行为,再与预期的延迟目标对比。测试结果同时为自动扩缩容参数提供依据:理想状态下每个副本应运行在饱和吞吐之下 10%–20% 的裕量内,以保证延迟不恶化,并在新的推理服务器/模型副本就绪之前有足够容量吸收突发负载。
测试时的两个关键实践:
- 跟踪多个延迟分位数(p50、p90、p95、p99),根据应用需求选取分位,更高分位数能反映请求间的性能波动;
- 区分闭环与开环负载测试。闭环(closed-loop)模式下,虚拟用户发出请求后要等待响应才发下一个;开环(open-loop)模式则动态实例化用户以维持恒定 RPS。闭环模式在系统过载、延迟飙升时会出现"协同遗漏"(coordinated omission):虚拟用户因等待变慢而自发降低实际负载,导致报告出的延迟显著低于生产环境真实值。因此确定饱和点推荐使用开环测试——每个阶段先线性爬升到目标 RPS,再恒定保持一段时间,各阶段目标 RPS 单调递增。
单副本负载饱和点(load saturation point)定义为单个模型副本能够持续承载的最大吞吐;超过该点后,瓶颈处(模型处理步骤、CPU/内存/网络等资源争用)排队会导致延迟退化。为得到可复现的结果,建议把被测模型的推理服务器部署在不被其他组件大量占用计算、内存、IO 资源的节点上。
仓库自带 k6 压测框架印证了这一方法。从 tests/k6/README.md 可以看到,项目提供了"恒定吞吐测试"(constant rate)等场景脚本,例如直接对模型服务做恒定 RPS 的开环压测:
MODEL_NAME="iris" MODEL_TYPE="iris" INFER_HTTP_ENDPOINT="http://172.31.255.9" k6 run scenarios/model_constant_rate.js
# 本地 gRPC 方式
INFER_TYPE="grpc" MODEL_TYPE="iris" k6 run scenarios/model_constant_rate.js
框架通过环境变量(如 INFER_GRPC_ENDPOINT、INFER_HTTP_ENDPOINT、MAX_NUM_MODELS)控制压测入口与规模,并支持"加载模型后直接推理、跳过卸载"等组合开关(如 INFER_HTTP_ITERATIONS=10 SKIP_UNLOAD_MODEL=1 MODEL_TYPE="tfsimple" k6 run -u 10 -i 500 scenarios/load_predict_unload.js),便于分别测量模型加载、推理、卸载各阶段的性能。
2.2 基础设施:CPU vs GPU 与 CPU 规格配置
模型整体性能受制于底层硬件及其使用方式:
- CPU 通常足以支撑轻量模型:树模型、回归模型、小型神经网络;
- GPU 更适合深度学习模型、大规模神经网络和 LLM——矩阵运算密集型模型(Transformer、CNN)从 GPU 加速中获益最大。
若成本敏感,推荐先用 CPU 起步,配合性能剖析工具(如 py-spy、scalene 之类的 profiling/监控工具)定位 CPU 瓶颈,再按需迁移到 GPU。
当模型需要处理大量并发请求时,单个 CPU 往往是瓶颈,此时应增加并行 worker 数(见下一节)。但需要注意:worker 数不应显著高于可用 CPU 核数,以降低进程间争用——每个 worker 运行在独立进程中,该点对同步(synchronous)模型最关键,因为后续处理会阻塞在每个请求完成之上。
更进阶的 CPU 利用方式是通过环境变量配置线程亲和性,控制线程如何绑定到物理处理器上。例如使用 OpenMP 的技术可以设置 KMP_AFFINITY 与 OMP_NUM_THREADS。不同 ML 框架通常都有自己的资源利用建议。此外,增大模型可用 RAM 可改善内存密集型模型的性能,例如参数量大、需要高维数据处理或复杂中间计算的模型。
2.3 推理执行:协议选择、批处理与并行 Worker
在优化部署模型或流水线的延迟/吞吐时,推理执行方式本身是重要变量:
- gRPC 可能比 REST 更高效,当请求负载适合二进制序列化格式时;
- 自适应批处理:把多个实时请求组织成小批次,可在保持可接受延迟的同时提升吞吐(MLServer 提供 adaptive batching 能力);
- 降低输入维度:减小输入尺寸可加速处理,同时减少模型部署周围的(反)序列化开销;
- 配置并行度:MLServer 部署的模型可通过
parallel_workers配置独立进程组成的 worker 池来执行推理,parallel_workers应与 Server pod 分配的 CPU 核数对齐,对同步模型、CPU 密集型异步模型以及高吞吐的 I/O 密集型异步模型效果最明显。
关于 worker 数量的评估方法:先测出单 worker 的最大吞吐(one-worker max throughput),再测 N 个 worker 的最大吞吐(n_workers max throughput)。注意 n_workers max throughput 并不等于 n_workers × one-worker max throughput,因为 worker 是独立进程,操作系统能并行运行的进程数受 CPU 核数限制——若所有 worker 都是 CPU 密集型,把 n_workers 设得高于 CPU 核数并不会带来额外并行。反之,若部分 worker 在等待 I/O 或 GPU,把 n_workers 设得高于 CPU 核数反而有助于提升吞吐,因为有些 worker 等待时其他 worker 可继续处理。
调优判断逻辑可归纳为:
| 请求速率区间 | 现象 | 调优建议 |
|---|---|---|
低于 one-worker max throughput |
未饱和 | 增加 worker 无益 |
低于 n_workers worker max throughput(pod 核数 > n_workers) |
未饱和,延迟恒定 | 维持现状 |
| 系统饱和、请求开始排队 | 延迟退化 | 在 pod 有富余 CPU 的前提下,把 worker 数提升到 CPU 核数附近或略高 |
其中,CPU 密集型和阻塞式(blocking)模型从增加 worker 中获益最大——增加 worker 的目标是用足够多的并行任务消化更高吞吐,同时最小化排队。
2.4 模型工件本身的优化
若目标是提速,模型工件自身的影响可能很大:给定输入下模型返回结果的速度取决于模型架构、模型规模、权重精度与输入尺寸。可考虑的优化方向包括:
- 模型剪枝(pruning):裁掉不重要的参数,在不显著影响输出质量的前提下减小模型规模;
- 量化(quantization):使用更低精度的数据类型表示权重与激活,降低推理的计算与内存开销;
- 输入降维(dimensionality reduction):降低计算复杂度;
- 高效模型架构:如 MobileNet、EfficientNet、DistilBERT 等专为快速推理、精度损失小而设计的架构;
- 优化的模型格式与运行时:如 ONNX Runtime、TensorRT、OpenVINO 等,利用硬件特定加速提升性能。
三、Pipeline 层性能调优
3.1 测试 Pipeline:先隔离模型,再定位关键路径
要理解 Pipeline 在生产环境中的行为,第一步是把测试隔离到 Pipeline 内的各个模型。先取得每个模型在其基础设施(如 GPU 规格)与配置(如 worker 数)下的最大吞吐,就能判断哪些模型可能拖累整个 Pipeline 的性能。在此基础上,优先按 Model 调优 一节的方法为各模型优化到目标性能——保证足够的副本数、正确的运行基础设施。
Seldon Core 2 Pipeline 的性能行为比单个模型更复杂,其推理请求延迟可以分解为两部分:
- Pipeline 关键路径(critical path,即包含瓶颈的路径)上各模型延迟之和;
- Pipeline 特有操作(数据移动、拷贝、写入 Kafka 主题等)造成的每阶段数据处理开销。
线性 Pipeline(顺序组件链)的吞吐规律较简单:
Pipeline 可达到的最大吞吐等于链上每个模型在给定 worker 数下最大吞吐的最小值。超过该最大值就会在处理中形成瓶颈、拉低整个 Pipeline 的表现。消除某一步瓶颈的手段包括:增加该模型 Server 的资源与 worker 数;增加该模型的副本数以分摊负载(自动扩缩容可以辅助确定正确副本数)。
对于更复杂的非线性 Pipeline,第一步是识别关键路径(其上的模型最先达到吞吐饱和),然后对该路径上制造瓶颈的模型执行同样的优化。Pipeline 中必然存在瓶颈模型,调优目标是平衡每个模型的副本数与 MLServer 资源(以及 worker 数),使各阶段都能以尽量少的阶段间排队处理请求吞吐,从而降低整体延迟。
3.2 降低数据处理开销:Kafka 分区与 Gateway 副本
调优 Pipeline 时,降低负责数据处理的 Core 2 组件的开销是另一条主线。Core 2 中有四个组件影响这部分开销:
pipelinegateway:处理 Pipeline 请求;modelgateway:向模型推理服务器发送请求;dataflow-engine:运行 Kafka KStream 拓扑,管理 Pipeline 内模型间流转的数据;- Kafka 集群本身。
由于 Core 2 使用 Kafka 作为模型间通信的消息系统,降低 Core 2 与 Kafka 之间的网络延迟(尤其当 Kafka 部署在独立集群时)能直接改善 Pipeline 性能。
此外,每个主题的 Kafka 分区数(必须对 Pipeline 内所有模型保持一致)显著影响:
- Kafka 的最大吞吐;
pipelinegateway、dataflow-engine、modelgateway的有效副本数。
作为跨多 Pipeline 提供高推理 RPS 的基线,推荐让 pipelinegateway 与 dataflow-engine 的副本数达到 Kafka 主题分区数,以利用推理流量的均衡分布——此时每个 dataflow-engine 处理来自一个分区、覆盖所有 Pipeline 的数据。继续增加 dataflow-engine 副本数会把 Pipeline 分片到可用副本上,每个 Pipeline 由 maxShardCountMultiplier 个副本处理(见下一节扩展机制)。
modelgateway 则可通过两个环境变量扩展其处理能力:
| 环境变量 | 作用 |
|---|---|
MODELGATEWAY_NUM_WORKERS |
每个 consumer 创建的轻量推理 worker(goroutine)数量,负责把请求转发给推理服务器并等待响应 |
MODELGATEWAY_MAX_NUM_CONSUMERS |
consumer 数量上限,即哈希表大小,决定多少个模型共享同一个 consumer |
源码中可以看到这两个参数的定义与默认值:modelgateway.yaml 中声明 MODELGATEWAY_NUM_WORKERS=8、MODELGATEWAY_MAX_NUM_CONSUMERS=100;manager.go 中定义了 EnvMaxNumConsumers = "MODELGATEWAY_MAX_NUM_CONSUMERS" 与 DefaultMaxNumConsumers = 100,infer.go 中定义了 EnvVarNumWorkers = "MODELGATEWAY_NUM_WORKERS"。每个命名空间内的模型基于其在大小等于 MODELGATEWAY_MAX_NUM_CONSUMERS 的哈希表中的索引,一致地分配给某一个 modelgateway consumer——哈希表越大,单个 consumer 分担的模型越少。调优顺序建议是先增加 worker 数,前提是 modelgateway pod 有足够资源支撑更多 worker 与 consumer。
3.3 横向扩展机制:maxShardCountMultiplier 与副本数计算
Core 2 对 dataflow engine、model gateway、pipeline gateway 均支持完全的横向扩展:每个服务通过一致性哈希自动把 Pipeline 或模型分配到各副本上,无需手动分配工作负载。Pipeline/模型只加载在副本的一个子集上,并在以下情形自动再平衡:
- 副本数扩/缩;
- 部署或删除 Pipeline / 模型。
决定单个 Pipeline/模型最多加载到多少个副本上的参数是 maxShardCountMultiplier,配置在 SeldonConfig 的 config.scalingConfig.pipelines.maxShardCountMultiplier 下(seldonconfig_types.go 中的字段注释说明它影响推理负载在副本间的分片方式)。通过 Helm 安装时,该参数默认取 {{ .Values.kafka.topics.numPartitions }}。
关键约束:Kafka 分区数就是 maxShardCountMultiplier 应设置的上限。超出分区数设置不仅没有额外性能收益,还可能导致丢请求——因为多出来的副本会接收到请求却在消费者组内不管理任何 Kafka 分区。该参数可在集群运行期间变更,新值约 1 分钟内传播到所有组件,触发 Pipeline/模型在三类副本间再平衡,并可能根据 Kafka 分区分配策略引入停机;若 Kafka 版本支持协作式再平衡(cooperative rebalancing),把分区分配策略设为 cooperative-sticky 可将扰动降到最小,dataflow-engine 默认即使用协作式再平衡策略。
三个组件的扩展上限汇总:
| 组件 | 扩展维度(上限为 maxShardCountMultiplier = 分区数) |
实际使用副本数 |
|---|---|---|
| Dataflow engine | #pipelines × maxShardCountMultiplier(受配置副本数封顶) |
min(replicas, pipelines × partitions) |
| Model gateway | #models × maxShardCountMultiplier(受 replicas 与 maxNumConsumers 封顶) |
min(replicas, min(models, maxNumConsumers) × partitions) |
| Pipeline gateway | #pipelines × maxShardCountMultiplier(受 replicas 与 maxNumConsumers 封顶) |
min(replicas, min(pipelines, maxNumConsumers) × partitions) |
这一上限逻辑在 operator 中有直接实现。runtime_reconciler.go 中的 replicaCalc 函数:
func replicaCalc(resourceCount, maxConsumers, partitions int32) int32 {
if resourceCount == 0 {
return 1
}
return partitions * min(resourceCount, maxConsumers)
}
当请求副本数超过该上限时,reconciler 会把副本数钳制到 maxReplicas 并发出告警事件(如 DataflowEngineReplicasAdjusted)。同文件还定义了默认值 DEFAULT_MAX_SHARD_COUNT_MULTIPLIER = 1 与 DEFAULT_PIPELINEGATEWAY_MAX_NUM_CONSUMERS = 100,与文档中 maxNumConsumers 默认 100 的说明一致。
副本数计算示例(以 spec.replicas 上限、maxShardCountMultiplier 与 Kafka 分区数代入上表公式):
Dataflow engine(3 个分区场景,multiplier=4,spec.replicas=9):
| 已部署 Pipeline 数 | 最终 dataflow 副本 |
|---|---|
| 3 | min(9, 3×4=12) → 9 |
| 2 | min(9, 2×4=8) → 8 |
| 1 | min(9, 1×4=4) → 4 |
Model gateway(multiplier=4,spec.replicas=20,maxNumConsumers=100):
| 已部署模型数 | 最终 modelgateway 副本 |
|---|---|
| 5 | min(20, min(5,100)×4=20) → 20 |
| 1 | min(20, min(1,100)×4=4) → 4 |
Pipeline gateway(multiplier=4,spec.replicas=10,maxNumConsumers=100):
| 已部署 Pipeline 数 | 最终 pipelinegateway 副本 |
|---|---|
| 8 | min(10, min(8,100)×4=32) → 10 |
| 2 | min(10, min(2,100)×4=8) → 8 |
| 1 | min(10, min(1,100)×4=4) → 4 |
各组件的加载/卸载行为由 CR 状态确认:
- Dataflow engine:Pipeline 加载/卸载随 Pipeline CR 的 load/unload 执行,调度器通过 CR 的
Pipelinestatus 确认结果;Pipeline 被标记为 ready 的前提是Pipelineready 状态满足; - Model gateway:模型加载/卸载随 Model CR 执行,调度器通过
ModelGwstatus 确认。注意ModelGwstatus 不代表模型可用的条件——模型在专用服务器上加载成功即可推理;ModelGwstatus 只对 Pipeline 或用户想走异步路径(向模型输入主题写请求、从输出主题读响应)时才有意义。在 Pipeline 语境下,ModelReady状态是"模型在服务器上可用"与"模型已在 modelgateway 上成功加载"的合取; - Pipeline gateway:同理通过
PipelineGwstatus 确认,且 Pipeline 被标记 ready 必须满足PipelineGwready 状态。
工作负载分配采用一致性哈希,保证负载均衡但不保证严格的一一对应——即使副本数等于 pipelines × partitions,各副本承担的 Pipeline 数也可能略有偏差,实践中分布是统计均匀的。再平衡在后台进行,无需人工介入;未使用的副本会自动缩容,随 Pipeline/模型增加自动扩容直至封顶。
四、调优路径小结
把上面的方法落到一张可执行的检查清单:
- 建基线:用开环负载测试确定每个模型单副本的饱和吞吐与延迟分位数(p50–p99),测试环境资源隔离;
- 调模型:按延迟/吞吐需求选择 CPU 或 GPU,
parallel_workers对齐 CPU 核数,评估 gRPC、批处理、输入降维的适用性,必要时做剪枝/量化/换运行时; - 调扩缩容:以饱和吞吐下 10%–20% 的裕量设定 autoscaling 目标,让副本在扩容窗口内吸收突发;
- 调 Pipeline:用各模型性能画像定位关键路径上的瓶颈模型,回到第 2 步优化该模型;
- 调数据链路:把
pipelinegateway/dataflow-engine副本数对齐 Kafka 分区数,优先增大MODELGATEWAY_NUM_WORKERS,maxShardCountMultiplier不超过分区数,需要低扰动再平衡时启用cooperative-sticky分区分配策略。
本文所有结论均出自仓库内的 性能调优文档 及其子文档、k6 压测框架 与 scheduler/operator 源码,读者可据此在当前仓库中继续深入各组件的实现细节。


