Apache Camel × AI ― サービングによる推論 #3: KServe
前回、前々回の記事でも紹介した通り、先日リリースされたApache Camel 4.10 LTSではAIモデルサービングに関する3つの新しいコンポーネントが追加されました。[1]
これまでTorchServe、TensorFlow Servingコンポーネントを紹介しました(記事1、記事2)。今回はKServeコンポーネントを紹介します。
KServeコンポーネント
KServeは、Kubernetes上でAIモデルをサービングするためのプラットフォームです。KServeには、クライアントからモデルサーバーにヘルスチェック・メタデータ取得・推論を行うためのAPIプロトコルが定義されており、そのKServe API [2] を通してKServeに準拠したモデルサーバーを統一的に呼び出すことができます。Camel KServeコンポーネントを使用すると、CamelルートからKServe APIを介してモデルサーバーへ推論をリクエストできます。
準備
改めて、まだCamel CLIがインストールされていなければインストールしてください。
jbang app install camel@apache/camel
インストールできたか動作確認をします。
$ camel --version
4.10.2
モデルの準備とサーバー立ち上げ
KServeコンポーネントを試すには、KServe Open Inference Protocol V2をサポートするモデルサーバーが必要です。OpenVINOやTritonなど、いくつかのモデルサーバーが利用できます。この記事では、Triton Inference ServerのDockerイメージを使用します。Triton Inference Serverのイメージは、amd64
だけでなくarm64
にも対応しているため、macOSユーザーも簡単に試すことができます。
KServe APIにはモデルを登録する操作はないため、サーバー起動時に予めモデルもロードします。本記事では、Triton Inference Serverのリポジトリで提供されているデモ用のモデルsimple
を使用します。このモデルの詳細は、後述の「推論」セクションで説明します。
Triton Inference Serverリポジトリからsimpleディレクトリを丸ごとダウンロードして、それをmodels
ディレクトリの下に置いてください。
ダウンロードしたsimple
をmodels
の下に置いたら、models
ディレクトリがある場所から以下のコマンドでコンテナーを起動します。
docker run --rm --name triton \
-p 8000:8000 \
-p 8001:8001 \
-p 8002:8002 \
-v ./models:/models \
nvcr.io/nvidia/tritonserver:25.02-py3 \
tritonserver --model-repository=/models
サーバー/モデルへの管理系操作
KServe Open Protocol V2が定義する推論以外の管理系操作は大きく2つに分かれます。
- サーバーへの操作
- ステータスチェック/死活確認(Server Ready / Server Live API)
- メタデータ取得(Server Metadata API)
- モデルへの操作
- ステータスチェック(Model Ready API)
- メタデータ取得(Model Metadata API)
それぞれCamelルートからどうやって呼び出せるかを見ていきます。
サーバーのステータスチェック
Camelルートから、サーバーが起動し準備ができているかを次のエンドポイントで確認できます。
kserve:server/ready
//DEPS org.apache.camel:camel-bom:4.10.2@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-kserve
import org.apache.camel.builder.RouteBuilder;
public class server_ready extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:server-ready?repeatCount=1")
.to("kserve:server/ready")
.log("Ready: ${body.ready}");
}
}
Camel CLIから以下のように実行します。
camel run server_ready.java
成功すれば、以下のようにサーバーのステータスを確認できます。
Ready: true
サーバーの死活確認
Camelルートからサーバーが稼働しているかを確認するには、次のエンドポイントを使います。
kserve:server/live
//DEPS org.apache.camel:camel-bom:4.10.2@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-kserve
import org.apache.camel.builder.RouteBuilder;
public class server_live extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:server-live?repeatCount=1")
.to("kserve:server/live")
.log("Live: ${body.live}");
}
}
Camel CLIから以下のように実行します。
camel run server_live.java
成功すれば、以下のようにサーバーの死活状態を確認できます。
Live: true
サーバーのメタデータ取得
Camelルートからサーバーのメタデータを取得するには、次のエンドポイントを使います。
kserve:server/metadata
//DEPS org.apache.camel:camel-bom:4.10.2@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-kserve
import org.apache.camel.builder.RouteBuilder;
public class server_metadata extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:server-metadata?repeatCount=1")
.to("kserve:server/metadata")
.log("Metadata:\n${body}");
}
}
Camel CLIから以下のように実行します。
camel run server_metadata.java
成功すれば、以下のようにサーバーのメタデータを取得できます。
Metadata:
name: "triton"
version: "2.55.0"
extensions: "classification"
extensions: "sequence"
extensions: "model_repository"
extensions: "model_repository(unload_dependents)"
extensions: "schedule_policy"
extensions: "model_configuration"
extensions: "system_shared_memory"
extensions: "cuda_shared_memory"
extensions: "binary_tensor_data"
extensions: "parameters"
extensions: "statistics"
extensions: "trace"
extensions: "logging"
モデルのステータスチェック
モデルが推論可能な状態にあるかどうかを、次のエンドポイントで確認できます。
kserve:model/ready?modelName=simple&modelVersion=1
//DEPS org.apache.camel:camel-bom:4.10.2@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-kserve
import org.apache.camel.builder.RouteBuilder;
public class model_ready extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:model-ready?repeatCount=1")
.to("kserve:model/ready?modelName=simple&modelVersion=1")
.log("Ready: ${body.ready}");
}
}
Camel CLIから以下のように実行します。
camel run model_ready.java
成功すれば、以下のようにモデルのステータスを確認できます。
Ready: true
モデルのメタデータ取得
これまで見てきたTorchServeやTensorFlow Servingと同様、AIモデルを呼び出すにはその入出力のシグネチャーを理解することが重要です。そのために、モデルのメタデータを取得する必要があります。
メタデータを取得するのは一度だけでいいので、通常は次のREST APIを直接呼び出してJSON形式でモデルシグネチャーを確認します(simple
モデルの場合)。
http://localhost:8000/v2/models/simple/versions/1
Camelルートからモデルのメタデータを確認するには、次のエンドポイントを使います。
kserve:model/metadata?modelName=simple&modelVersion=1
//DEPS org.apache.camel:camel-bom:4.10.2@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-kserve
import org.apache.camel.builder.RouteBuilder;
public class model_metadata extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:model-metadata?repeatCount=1")
.to("kserve:model/metadata?modelName=simple&modelVersion=1")
.log("Metadata:\n${body}");
}
}
Camel CLIから以下のように実行します。
camel run model_metadata.java
成功すれば、以下のようにモデルのメタデータを取得できます。
Metadata:
name: "simple"
versions: "1"
platform: "tensorflow_graphdef"
inputs {
name: "INPUT0"
datatype: "INT32"
shape: -1
shape: 16
}
inputs {
name: "INPUT1"
datatype: "INT32"
shape: -1
shape: 16
}
outputs {
name: "OUTPUT0"
datatype: "INT32"
shape: -1
shape: 16
}
outputs {
name: "OUTPUT1"
datatype: "INT32"
shape: -1
shape: 16
}
推論
KServeでモデルを呼び出して推論をさせてみましょう。ここでは、simple
モデルを呼び出して非常に簡単な計算を実行します。
「モデルのメタデータ取得」セクションで確認したように、simple
モデルはサイズ16の2つのINT32
リスト(INPUT0
とINPUT1
)を入力として受け取り、サイズ16の2つのINT32
リストを出力(OUTPUT0
とOUTPUT1
)として返します。このモデルは単純に、INPUT0
とINPUT1
の各要素を加算した結果をOUTPUT0
として、INPUT0
とINPUT1
の各要素を減算した結果をOUTPUT1
として返します。
以下のサンプルコードでは、次の入力をモデルに与えます。
INPUT0 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
INPUT1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
そして同様に、次の出力を結果として受け取ることになります。
OUTPUT0 = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31]
OUTPUT1 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Simpleモデルの呼び出し
推論には以下のエンドポイントを使います。
kserve:infer?modelName=simple&modelVersion=1
//DEPS org.apache.camel:camel-bom:4.10.2@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-kserve
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import com.google.protobuf.ByteString;
import inference.GrpcPredictV2.InferTensorContents;
import inference.GrpcPredictV2.ModelInferRequest;
import inference.GrpcPredictV2.ModelInferResponse;
public class infer_simple extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:infer-simple?repeatCount=1")
.setBody(constant(createRequest()))
.to("kserve:infer?modelName=simple&modelVersion=1")
.process(this::postprocess)
.log("Result[0]: ${body[0]}")
.log("Result[1]: ${body[1]}");
}
ModelInferRequest createRequest() {
var ints0 = IntStream.range(1, 17).boxed().collect(Collectors.toList());
var content0 = InferTensorContents.newBuilder().addAllIntContents(ints0);
var input0 = ModelInferRequest.InferInputTensor.newBuilder()
.setName("INPUT0").setDatatype("INT32").addShape(1).addShape(16)
.setContents(content0);
var ints1 = IntStream.range(0, 16).boxed().collect(Collectors.toList());
var content1 = InferTensorContents.newBuilder().addAllIntContents(ints1);
var input1 = ModelInferRequest.InferInputTensor.newBuilder()
.setName("INPUT1").setDatatype("INT32").addShape(1).addShape(16)
.setContents(content1);
return ModelInferRequest.newBuilder()
.addInputs(0, input0).addInputs(1, input1)
.build();
}
void postprocess(Exchange exchange) {
var response = exchange.getMessage().getBody(ModelInferResponse.class);
var outList = response.getRawOutputContentsList().stream()
.map(ByteString::asReadOnlyByteBuffer)
.map(buf -> buf.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer())
.map(buf -> {
var ints = new ArrayList<Integer>(buf.remaining());
while (buf.hasRemaining()) {
ints.add(buf.get());
}
return ints;
})
.collect(Collectors.toList());
exchange.getMessage().setBody(outList);
}
}
Camel CLIから以下のように実行します。
camel run infer_simple.java
成功すれば、以下のような結果が得られるでしょう。上で説明した通りの計算結果が得られています。
Result[0]: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31]
Result[1]: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
まとめ
これまで、最新のCamel 4.10 LTSリリースで追加されたAIモデルサービングコンポーネントを3回にわたって紹介してきました。今回のKServeコンポーネントで最後になります。
TorchServe、TensorFlow Servingに加え、KServeコンポーネントを使うことで、現在主流のAIモデルサーバーの大部分をカバーできるでしょう。Camel 4.10 LTSで、Camelとモデルサーバーを組み合わせてインテグレーションを構築する準備が整ったといえます。
KServeはさらに、Kubernetes上でMLOpsを構築する際のモデルサービングのデファクトになりつつあるAPIです。KubeflowなどのMLOpsプラットフォーム上で構築しているAIシステムに、AIモデルのアプリケーション側としてCamelのインテグレーションを活用できるようになったということです。
Apache Camel×AIを使って、ぜひインテリジェントなインテグレーションの可能性を試してみてください。
サンプルコード
今回紹介したCamel×AIのサンプルコードは、このリポジトリで公開しています。
-
Camel TorchServeコンポーネントは4.9から。 ↩︎
Discussion