☰
Seldon Core 推理实战:通过 REST 与 gRPC 调用 TensorFlow 模型与 Pipeline
2026/10/14 17:26:54 网站建设 项目流程
  • 人工智能
  • 大模型
  • MLOps
  • LLMOps
  • 模型推理服务
  • 云原生
  • 后端

【免费下载链接】seldon-core

An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models

项目地址:https://gitcode.com/gh_mirrors/se/seldon-core
点击查看免费下载

本文基于 seldon-core 仓库中的推理示例文档(inference.md),完整演示如何在 seldon-core v2 数据面上完成两类典型推理操作:对单个 TensorFlow 模型(tfsimple1)发起推理、对由该模型组成的单步 Pipeline(tfsimple)发起推理,并分别使用 seldon CLI、curl 和 grpcurl 三种客户端,覆盖 REST 与 gRPC 两种协议。读完本文,你将掌握 v2 推理请求的完整链路:模型/Pipeline 的加载与就绪检查、推理端点的请求格式、seldon-model路由头在数据面中的工作原理,以及 CLI 各参数的底层实现。

环境前提

示例统一将推理入口指向本地数据面端点:

%env INFER_ENDPOINT=0.0.0.0:9000
env: INFER_ENDPOINT=0.0.0.0:9000

从源码可以印证这个默认值:CLI 的--inference-host参数默认读取环境变量SELDON_INFER_HOST,未设置时回退到0.0.0.0:9000,见 flags.go:

// Env vars const ( envInfer = "SELDON_INFER_HOST" ... ) // Defaults const ( defaultInferHost = "0.0.0.0:9000" ... )

因此下文命令中省略--inference-host时,CLI 也会默认访问0.0.0.0:9000。

一、TensorFlow 模型推理

1.1 准备并加载模型

仓库中自带示例模型定义 samples/models/tfsimple1.yaml:

cat ./samples/models/tfsimple1.yaml
apiVersion: mlops.seldon.io/v1alpha1 kind: Model metadata: name: tfsimple1 spec: storageUri: "gs://seldon-models/triton/simple" requirements: - tensorflow memory: 100Ki

关键字段说明:

  • storageUri:模型存储地址,seldon-core 支持 GCS(gs://)、S3(s3://)以及本地路径等,由 Agent 上的 rclone 机制拉取到本地;
  • requirements:声明该模型需要 TensorFlow 推理后端,调度器据此决定可用的 Server;
  • memory:模型的内存限额(Ki/Mi/Gi 单位),供调度器做资源匹配。

加载模型:

seldon model load -f ./samples/models/tfsimple1.yaml
{}

空对象表示控制面(Scheduler)接受了加载请求。随后等待模型变为ModelAvailable状态:

seldon model status tfsimple1 -w ModelAvailable | jq -M .

-w(--wait)参数会持续轮询直到指定条件成立,示例输出为ModelAvailable状态信息。

1.2 使用 seldon CLI 发起 REST 推理

seldon model infer tfsimple1 --inference-host ${INFER_ENDPOINT} \ '{"inputs":[{"name":"INPUT0","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]}]}'

响应:

{ "model_name": "tfsimple1_1", "model_version": "1", "outputs": [ { "name": "OUTPUT0", "datatype": "INT32", "shape": [1, 16], "data": [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32] }, { "name": "OUTPUT1", "datatype": "INT32", "shape": [1, 16], "data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] } ] }

响应格式遵循 Triton v2 推理协议(见 v2_dataplane.proto)。tfsimple是一个演示用加法模型:OUTPUT0为INPUT0 + INPUT1(1+1=2,2+2=4,…,16+16=32),OUTPUT1恒为零。注意model_name返回的是带版本后缀的tfsimple1_1,即模型名加版本号。

1.3 使用 seldon CLI 发起 gRPC 推理

切换协议只需加--inference-mode grpc,并且请求体使用 protojson 序列化格式(字段名为驼峰式,张量数据放在contents.intContents中):

seldon model infer tfsimple1 --inference-mode grpc --inference-host ${INFER_ENDPOINT} \ '{"model_name":"tfsimple1","inputs":[{"name":"INPUT0","contents":{"intContents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","contents":{"intContents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]}]}'
{"modelName":"tfsimple1_1","modelVersion":"1","outputs":[{"name":"OUTPUT0","datatype":"INT32","shape":["1","16"],"contents":{"intContents":[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32]}},{"name":"OUTPUT1","datatype":"INT32","shape":["1","16"],"contents":{"intContents":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}}]}

从源码可以看到,CLI 的 gRPC 分支会将输入按 protojson 反序列化为ModelInferRequest,并自动覆写model_name为命令行传入的资源名,然后调用 proto 中定义的inference.GRPCInferenceService/ModelInfer方法(req.ModelName = resourceName及grpcClient.ModelInfer(ctx, req, ...),见 infer.go)。另外,gRPC 响应中的rawOutputContents(原始字节)会在客户端按datatype解码为结构化内容(updateResponseFromRawContents,infer.go),所以最终打印的是可读的contents字段。

1.4 使用 curl 发起 REST 推理

curl http://${INFER_ENDPOINT}/v2/models/tfsimple1/infer -H "Content-Type: application/json" -H "seldon-model: tfsimple1" \ -d '{"inputs":[{"name":"INPUT0","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]}]}'
{"model_name":"tfsimple1_1","model_version":"1","outputs":[{"name":"OUTPUT0","datatype":"INT32","shape":[1,16],"data":[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32]},{"name":"OUTPUT1","datatype":"INT32","shape":[1,16],"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}]}

REST 路径与 CLI 完全一致:POST/v2/models/{model}/infer。这里显式携带的seldon-model头是关键——它是数据面 Envoy 做路由匹配的根据(下文第五节详述)。

1.5 使用 grpcurl 发起 gRPC 推理

grpcurl 直接以 protobuf 描述文件调用 gRPC 服务。示例中的-import-path ../apis是相对于 samples 目录的路径,即指向仓库的 apis 目录:

grpcurl -d '{"model_name":"tfsimple1","inputs":[{"name":"INPUT0","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]}]}' \ -plaintext \ -import-path ../apis \ -proto ../apis/mlops/v2_dataplane/v2_dataplane.proto \ -rpc-header seldon-model:tfsimple1 \ ${INFER_ENDPOINT} inference.GRPCInferenceService/ModelInfer

响应中可见rawOutputContents(base64 编码的原始张量字节):

{ "modelName": "tfsimple1_1", "modelVersion": "1", "outputs": [ { "name": "OUTPUT0", "datatype": "INT32", "shape": ["1", "16"] }, { "name": "OUTPUT1", "datatype": "INT32", "shape": ["1", "16"] } ], "rawOutputContents": [ "AgAAAAQAAAAGAAAACAAAAAoAAAAMAAAADgAAABAAAAASAAAAFAAAABYAAAAYAAAAGgAAABwAAAAeAAAAIAAAAA==", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" ] }

grpcurl 未做 raw contents 的解码,所以输出展示的是原始字节;第一个 base64 串按小端 INT32 解码后正是[2,4,6,...,32]。-rpc-header对应 gRPC metadata,作用与 curl 中的 HTTP 头相同:向数据面声明本次请求访问的逻辑模型名。

二、Pipeline 推理

2.1 准备并加载 Pipeline

单步 Pipeline 定义见 samples/pipelines/tfsimple.yaml:

cat ./samples/pipelines/tfsimple.yaml
apiVersion: mlops.seldon.io/v1alpha1 kind: Pipeline metadata: name: tfsimple spec: steps: - name: tfsimple1 output: steps: - tfsimple1

steps声明 Pipeline 的节点(此处只有一个模型节点tfsimple1),output.steps声明哪些节点的输出作为 Pipeline 的最终输出。

加载并等待就绪:

seldon pipeline load -f ./samples/pipelines/tfsimple.yaml
{}
seldon pipeline status tfsimple -w PipelineReady
{"pipelineName":"tfsimple","versions":[{"pipeline":{"name":"tfsimple","uid":"cg5fm6c6dpcs73c4qhe0","version":1,"steps":[{"name":"tfsimple1"}],"output":{"steps":["tfsimple1.outputs"]},"kubernetesMeta":{}},"state":{"pipelineVersion":1,"status":"PipelineReady","reason":"created pipeline","lastChangeTimestamp":"2023-03-10T09:40:41.317797761Z","modelsReady":true}}]}

PipelineReady表示数据面已为该 Pipeline 建立完整的 xDS 配置(路由与集群),且modelsReady: true说明其依赖的模型均可用。

2.2 使用 seldon CLI 发起 REST 推理

seldon pipeline infer tfsimple --inference-host ${INFER_ENDPOINT} \ '{"inputs":[{"name":"INPUT0","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]}]}'
{ "model_name": "", "outputs": [ { "data": [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32], "name": "OUTPUT0", "shape": [1,16], "datatype": "INT32" }, { "data": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], "name": "OUTPUT1", "shape": [1,16], "datatype": "INT32" } ] }

与模型推理响应相比,Pipeline 响应的model_name为空——因为请求由 Pipeline 承接,输出归属 Pipeline 而非某个具体模型。

2.3 使用 seldon CLI 发起 gRPC 推理

seldon pipeline infer tfsimple --inference-mode grpc --inference-host ${INFER_ENDPOINT} \ '{"model_name":"tfsimple1","inputs":[{"name":"INPUT0","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]}]}'
{"outputs":[{"name":"OUTPUT0","datatype":"INT32","shape":["1","16"],"contents":{"intContents":[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32]}},{"name":"OUTPUT1","datatype":"INT32","shape":["1","16"],"contents":{"intContents":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}}]}

2.4 使用 curl 与 grpcurl 发起 Pipeline 推理

REST 请求路径仍使用其中某个成员模型名(tfsimple1),但seldon-model头的值改为tfsimple.pipeline,以此告诉数据面走 Pipeline 路由:

curl http://${INFER_ENDPOINT}/v2/models/tfsimple1/infer -H "Content-Type: application/json" -H "seldon-model: tfsimple.pipeline" \ -d '{"inputs":[{"name":"INPUT0","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"datatype":"INT32","shape":[1,16]}]}'
{"model_name":"","outputs":[{"data":[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32],"name":"OUTPUT0","shape":[1,16],"datatype":"INT32"},{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"name":"OUTPUT1","shape":[1,16],"datatype":"INT32"}]}

grpcurl 同理,只把-rpc-header的值换成tfsimple.pipeline:

grpcurl -d '{"model_name":"tfsimple1","inputs":[{"name":"INPUT0","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]},{"name":"INPUT1","contents":{"int_contents":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},"datatype":"INT32","shape":[1,16]}]}' \ -plaintext \ -import-path ../apis \ -proto ../apis/mlops/v2_dataplane/v2_dataplane.proto \ -rpc-header seldon-model:tfsimple.pipeline \ ${INFER_ENDPOINT} inference.GRPCInferenceService/ModelInfer

响应与 2.3 的 CLI gRPC 输出一致(含rawOutputContents原始字节字段)。

2.5 清理资源

seldon pipeline unload tfsimple seldon model unload tfsimple1
{} {}

注意卸载顺序:先卸载 Pipeline,再卸载其依赖的模型,避免 Pipeline 仍处于就绪状态时底层模型消失。

三、seldon-model路由头的工作原理

上面反复出现的seldon-model头是 seldon-core v2 数据面的核心路由机制,可以从源码链路完整印证:

1. 客户端生成头。CLI 中模型推理与 Pipeline 推理的差别就体现在头的值上:

func addSeldonModelHeaderToRequest(r *http.Request, inferType InferType, resourceName string) { var headerValue string switch inferType { case InferModel: headerValue = resourceName case InferPipeline: headerValue = resourceName + "." + SeldonPipelineHeader // 即 "<name>.pipeline" } r.Header.Set(SeldonModelHeader, headerValue) }

(见 infer.go,常量SeldonModelHeader = "seldon-model"、SeldonPipelineHeader = "pipeline"定义在 infer.go。)

2. 数据面按头匹配路由。Scheduler 的 Envoy xDS 缓存为每个模型和 Pipeline 生成独立的路由:HTTP 路由匹配路径前缀/v2,gRPC 路由匹配前缀/inference.GRPCInferenceService,同时用seldon-model头的精确值(exact match)区分逻辑目标;Pipeline 的精确值由getPipelineModelName生成为<pipelineName>.pipeline(见 resource.go 与 resource.go)。这就是为什么 curl 对 Pipeline 发请求时,URL 里写的是成员模型路径,而真正决定流量走向的是头中的tfsimple.pipeline。

3. 路由后改写内部模型头。匹配到 Pipeline 路由后,数据面会在转发时附加SeldonInternalModelHeader(值仍为<pipelineName>.pipeline),Pipeline Gateway 再据此在 Pipeline 内部按节点名(如tfsimple1)继续分发到各模型,最终把output.steps指定节点的输出聚合为 Pipeline 响应——这与状态 JSON 中output.steps: ["tfsimple1.outputs"]的声明相对应。

4. 响应头回传路由结果。路由动作会附加x-seldon-route响应头(ResponseHeadersToAdd,见 resource.go),CLI 用它统计多次推理时各版本/路由的命中次数(updateSummary/updateErrors,infer.go),在多版本与实验(Experiment)场景下用于观察流量分配。

四、seldon CLI 推理命令参数详解

结合 model_infer.go 与 pipeline_infer.go 中的 flag 定义,seldon model infer/seldon pipeline infer支持以下参数:

参数默认值说明
--inference-host环境变量SELDON_INFER_HOST,未设置时0.0.0.0:9000数据面推理入口
--inference-moderest推理协议,取值rest或grpc
-f, --file-path空从文件读取推理 payload;与命令行内联数据二选一
-r, --show-requestfalse打印发送的请求体
-o, --show-responsetrue打印响应体
-s, --sticky-sessionfalse复用上次推理的粘性会话路由(仅对实验有效)
-i, --iterations1重复推理次数,用于批量/压力式验证
-t, --seconds0按持续时间(秒)连续推理,与-i二选一
--show-headersfalse打印请求/响应头(REST)或 gRPC metadata
--header key=value空附加自定义头,可多次传入
--authority空HTTP/2 authority 或 HTTP/1 虚拟主机名

两点值得注意:

  • 内联数据与文件必须二选一,否则报错required inline data or from file with -f <file-path>(见 model_infer.go);
  • gRPC 连接自带指数退避重试拦截器(BackoffExponential(100 * time.Millisecond),见 infer.go),单次推理失败时(默认 1 次迭代)会直接返回错误,多迭代场景则计入错误统计继续执行。

五、小结

  • seldon-core v2 的推理请求统一走数据面端点(默认0.0.0.0:9000):REST 为POST /v2/models/{name}/infer,gRPC 为inference.GRPCInferenceService/ModelInfer,两者共享同一套 v2 张量请求/响应格式;
  • seldon-model路由头是区分“直接访问模型”与“通过 Pipeline 访问”的开关:模型推理头值为模型名,Pipeline 推理头值为<pipelineName>.pipeline,gRPC 侧通过 metadata 传递(grpcurl 的-rpc-header);
  • 模型/Pipeline 加载后需分别等待ModelAvailable/PipelineReady状态,推理才会在数据面完成端到端路由;
  • 若请求经 TLS 数据面(配置 Dataplane Tls 时),CLI 会自动使用 https/加密 gRPC 通道并支持证书双向校验,参数与本文命令形式不变,仅需调整--inference-host指向。
  • 人工智能
  • 大模型
  • MLOps
  • LLMOps
  • 模型推理服务
  • 云原生
  • 后端

【免费下载链接】seldon-core

An MLOps framework to package, deploy, monitor and manage thousands of production machine learning models

项目地址:https://gitcode.com/gh_mirrors/se/seldon-core
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询