Zenn
🐪

Apache Camel × AI ― サービングによる推論 #3: KServe

2025/03/28に公開

Camel AI

前回前々回の記事でも紹介した通り、先日リリースされた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をサポートするモデルサーバーが必要です。OpenVINOTritonなど、いくつかのモデルサーバーが利用できます。この記事では、Triton Inference ServerのDockerイメージを使用します。Triton Inference Serverのイメージは、amd64だけでなくarm64にも対応しているため、macOSユーザーも簡単に試すことができます。

KServe APIにはモデルを登録する操作はないため、サーバー起動時に予めモデルもロードします。本記事では、Triton Inference Serverのリポジトリで提供されているデモ用のモデルsimpleを使用します。このモデルの詳細は、後述の「推論」セクションで説明します。

Triton Inference Serverリポジトリからsimpleディレクトリを丸ごとダウンロードして、それをmodelsディレクトリの下に置いてください。

ダウンロードしたsimplemodelsの下に置いたら、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つに分かれます。

  1. サーバーへの操作
  2. モデルへの操作

それぞれCamelルートからどうやって呼び出せるかを見ていきます。

サーバーのステータスチェック

Camelルートから、サーバーが起動し準備ができているかを次のエンドポイントで確認できます。

kserve:server/ready
server_ready.java
//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
server_live.java
//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
server_metadata.java
//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
model_ready.java
//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
model_metadata.java
//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リスト(INPUT0INPUT1)を入力として受け取り、サイズ16の2つのINT32リストを出力(OUTPUT0OUTPUT1)として返します。このモデルは単純に、INPUT0INPUT1の各要素を加算した結果をOUTPUT0として、INPUT0INPUT1の各要素を減算した結果を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モデルの呼び出し
Simpleモデルの呼び出し

推論には以下のエンドポイントを使います。

kserve:infer?modelName=simple&modelVersion=1
infer_simple.java
//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のサンプルコードは、このリポジトリで公開しています。

https://github.com/megacamelus/camel-ai-examples

脚注
  1. Camel TorchServeコンポーネントは4.9から。 ↩︎

  2. KServe Open Inference Protocol V2 ↩︎

GitHubで編集を提案

Discussion

ログインするとコメントできます