LLMフレームワーク?「Jina」を試す
以下で紹介したJina AIのいろいろな製品のうち、
LLMフレームワークと思われる「Jina」を試してみる。?になってるのはドキュメント斜め読みしてもピンとこなかったので。
GitHubレポジトリの説明
Jina
クラウドネイティブテクノロジーでマルチモーダルAIアプリケーションを構築する
Jinaは、gRPC、HTTP、WebSocketを介して通信するマルチモーダルAIサービスとパイプラインを構築し、それらをスケールアップして本番環境にデプロイすることができる。インフラの複雑さを気にすることなく、ロジックとアルゴリズムに集中できる。
Jinaは、ローカルデプロイからDocker-Compose、Kubernetes、またはJina AI Cloudのような高度なオーケストレーションフレームワークに移行するMLモデルを提供するためのスムーズなPythonicエクスペリエンスを提供する。Jinaは、高度なソリューションエンジニアリングとクラウドネイティブテクノロジーをすべての開発者が利用できるようにする。
- あらゆるデータタイプと主流のディープラーニングフレームワークのモデルを構築し、提供する。
- 簡単なスケーリング、二重クライアント-サーバーストリーミング、バッチ処理、動的バッチ処理、非同期/ノンブロッキングデータ処理、あらゆるプロトコルを備えた高性能サービスを設計する。
- LLMモデルの出力をストリーミングしながら提供する。
- Executor HubによるDockerコンテナ統合、OpenTelemetry/Prometheusによる観測可能性。
- Jina AI CloudによるCPU/GPUホスティングの合理化。
- KubernetesとDocker Composeの統合により、独自のクラウドやシステムにデプロイできる。
Jinaの価値提案はFastAPIのそれとよく似ているように見えるかもしれない。しかし、いくつかの根本的な違いがある:
データ構造と通信プロトコル
- FastAPIの通信はPydanticに依存しており、JinaはDocArrayに依存しているため、Jinaはサービスを公開するために複数のプロトコルをサポートしている。gRPCプロトコルのサポートは、エンベッディングやテンソルをより効率的にシリアライズできるエンベッディング・サービスのように、データ集約的なアプリケーションに特に有用である。
高度なオーケストレーションとスケーリング機能
- Jinaは、あなたのサービスとモデルを簡単にコンテナ化し、オーケストレーションすることを可能にし、並行性とスケーラビリティを提供する。
- Jinaは、コンテナ化され、独立してスケーリングできる複数のマイクロサービスから形成されるアプリケーションをデプロイすることができる。
クラウドへの旅
- Jinaは、ローカル開発(DocArrayを使用)から、デプロイメントとフローを使用したローカルサービスへのスムーズな移行を提供し、コンテナのライフタイムをオーケストレーションするKubernetesのキャパシティを使用することで、プロダクション対応のサービスを持つことができる。
- Jina AIクラウドを使うことで、アプリケーションのスケーラブルでサーバーレスなデプロイメントに1つのコマンドでアクセスできる。
対抗としてはFastAPIになるのか
公式ドキュメント
Getting Startedをやってみる。今回はローカルのUbuntuで。仮想環境作成済とする。
インストール
$ pip install -U jina
Create First Projectを進める
Jina CLIを使ってプロジェクトを作成する。プロジェクトには2種類ある。
-
Deployment
- 単一のモデルやマイクロサービスにサービスを提供し、スケールさせることができる。
-
Flow
- Deploymentを処理パイプラインに接続することができる。
Deploymentでマイクロサービスを作って、Flowでそれを使ったワークフローを作るっていうことなのかな?とりあえずDeploymentでやってみる。
$ jina new hello-jina --type=deployment
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNNNNNNNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWNNNWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMWxxxxxxxxxOMMMMMNxxxxxxxxx0MMMMMKddddddxkKWMMMMMMMMMMMMXOxdddxONMMMM
MMMMMMMMMMMMXllllllllldMMMMM0lllllllllxMMMMMOllllllllllo0MMMMMMMM0olllllllllo0MM
MMMMMMMMMMMMXllllllllldMMMMM0lllllllllxMMMMMOlllllllllllloWMMMMMdllllllllllllldM
MMMMMMMMMMMMXllllllllldMMMMM0lllllllllxMMMMMOllllllllllllloMMMM0lllllllllllllllK
MMMMMMMMMMMMKllllllllldMMMMM0lllllllllxMMMMMOllllllllllllllKMMM0lllllllllllllllO
MMMMMMMMMMMMKllllllllldMMMMM0lllllllllxMMMMMOllllllllllllll0MMMMollllllllllllllO
MWOkkkkk0MMMKlllllllllkMMMMM0lllllllllxMMMMMOllllllllllllll0MMMMMxlllllllllllllO
NkkkkkkkkkMMKlllllllloMMMMMM0lllllllllxMMMMMOllllllllllllll0MMMMMMWOdolllllllllO
KkkkkkkkkkNMKllllllldMMMMMMMMWWWWWWWWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MOkkkkkkk0MMKllllldXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMWX00KXMMMMXxk0XMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
/SOMEWHERE/jina-test/.ve
nv/bin/jina new
hello-jina
--type=deployment
╭──────────┬────────────╮
│ Argument │ Value │
├──────────┼────────────┤
│ cli │ new │
│ name │ hello-jina │
│ type │ deployment │
╰──────────┴────────────╯
プロジェクトディレクトリが作成される
$ ls
hello-jina
ディレクトリ構成はこんな感じ。
$ tree hello-jina
hello-jina
├── __pycache__/
├── client.py
├── deployment.yml
└── executor1
├── __pycache__
├── config.yml
├── executor.py
└── requirements.txt
-
deployment.yml
- デプロイ用設定ファイル
-
executor1/
- Executorのコードを書く場所。
- Executorがモデルをラップして入出力できるようにしたものと考えれば良さそう。
-
config.yml
- Executorの設定ファイル。Executorのメタデータや依存関係を格納。
-
client.py
- Jinaプロジェクトのエントリポイント。
python client.py
で実行できる。
- Jinaプロジェクトのエントリポイント。
README.md
requirements.txt
細かいファイルの中身は置いといて、このDeploymentを起動してみる。
$ cd hello-jina
$ jina deployment --uses deployment.yml
╭───────────────────────┬──────────────────────────────────╮
│ Argument │ Value │
├───────────────────────┼──────────────────────────────────┤
│ allow-concurrent │ False │
│ cli │ deployment │
│ compression │ None │
│ connection-list │ None │
│ cors │ False │
│ deployment-role │ None │
│ description │ None │
│ disable-auto-volume │ False │
│ docker-kwargs │ None │
│ entrypoint │ None │
│ env │ None │
│ env-from-secret │ None │
│ exit-on-exceptions │ [] │
│ external │ False │
│ extra-search-paths │ [] │
│ floating │ False │
│ force-network-mode │ AUTO │
│ force-update │ False │
│ gpus │ None │
│ grpc-channel-options │ None │
│ grpc-metadata │ None │
│ grpc-server-options │ None │
│ host │ ['0.0.0.0'] │
│ image-pull-secrets │ None │
│ install-requirements │ False │
│ k8s-namespace │ None │
│ log-config │ default │
│ metrics │ False │
│ metrics-exporter-host │ None │
│ metrics-exporter-port │ None │
│ monitoring │ False │
│ name │ executor │
│ native │ False │
│ no-reduce │ False │
│ noblock-on-start │ False │
│ output-array-type │ None │
│ peer-ports │ None │
│ pod-role │ WORKER │
│ polling │ ANY │
│ port │ [62999] │
│ port-monitoring │ [62576] │
│ prefer-platform │ None │
│ protocol │ [<ProtocolType.GRPC: 0>] │
│ provider │ [<ProviderType.NONE: 0>] │
│ provider-endpoint │ None │
│ py-modules │ None │
│ quiet │ False │
│ quiet-error │ False │
│ raft-configuration │ None │
│ reload │ False │
│ replica-id │ 0 │
│ replicas │ 1 │
│ retries │ -1 │
│ runtime-cls │ WorkerRuntime │
│ shard-id │ 0 │
│ shards │ 1 │
│ ssl-certfile │ None │
│ ssl-keyfile │ None │
│ stateful │ False │
│ timeout-ctrl │ 60 │
│ timeout-ready │ 600000 │
│ timeout-send │ None │
│ title │ None │
│ tls │ False │
│ traces-exporter-host │ None │
│ traces-exporter-port │ None │
│ tracing │ False │
│ uses │ deployment.yml │
│ uses-after │ None │
│ uses-after-address │ None │
│ uses-before │ None │
│ uses-before-address │ None │
│ uses-dynamic-batching │ None │
│ uses-metas │ None │
│ uses-requests │ None │
│ uses-with │ None │
│ uvicorn-kwargs │ None │
│ volumes │ None │
│ when │ None │
│ workspace │ None │
│ workspace-id │ 8ca62fd399a34e08a86ab00d7fc96eed │
╰───────────────────────┴──────────────────────────────────╯
INFO toyExecutor/rep-0@1344840 start server bound to 0.0.0.0:63855 [05/18/24 08:18:13]
WARNI… toyExecutor/rep-0@1344840 endpoint GET on "/" is used for health checks, make sure it's still
accessible
INFO gateway@1344841 start server bound to 0.0.0.0:54321 [05/18/24 08:18:13]
======== Running on http://0.0.0.0:54322 ========
(Press CTRL+C to quit)
──────────────────────────────────────────── 🎉 Deployment is ready to serve! ─────────────────────────────────────────────
╭────────────── 🔗 Endpoint ───────────────╮
│ ⛓ Protocol grpc │
│ 🏠 Local 0.0.0.0:54321 │
│ 🔒 Private 192.XXX.XXX.XXX:54321 │
│ 🌍 Public 217.XXX.XXX.XXX:54321 │
│ ⛓ Protocol http │
│ 🏠 Local 0.0.0.0:54322 │
│ 🔒 Private 192.XXX.XXX.XXX:54322 │
│ 🌍 Public 217.XXX.XXX.XXX:54322 │
╰──────────────────────────────────────────╯
╭─────────── 💎 HTTP extension ────────────╮
│ 💬 Swagger UI 0.0.0.0:54322/docs │
│ 📚 Redoc 0.0.0.0:54322/redoc │
╰──────────────────────────────────────────╯
gRPCとHTTPで立ち上がっている。試しにHTTPでアクセスしてみる。
$ curl http://192.XXX.XXX.XXX:54322
{}
ブラウザでも。
普通にFastAPIが立ち上がっているっぽい。
gRPCでつなげてみる。client.py
を使う。
$ python client.py
['hello, world!', 'goodbye, world!']
お、こちらはなんか返ってきた。client.py
の中身を見てみる。
from jina import Client
from docarray import DocList
from docarray.documents import TextDoc
if __name__ == '__main__':
c = Client(host='grpc://0.0.0.0:54321')
da = c.post(
'/', DocList[TextDoc]([TextDoc(), TextDoc()]), return_type=DocList[TextDoc]
)
print(da.text)
executor1/executor.py
はこうなっていた。
from jina import Executor, requests
from docarray import DocList
from docarray.documents import TextDoc
class MyExecutor(Executor):
@requests
def foo(self, docs: DocList[TextDoc], **kwargs) -> DocList[TextDoc]:
docs[0].text = 'hello, world!'
docs[1].text = 'goodbye, world!'
return docs
どうやら入出力にはDocArrayを使うらしい。TextDocオブジェクトを2ついれたDocListオブジェクトを渡すと、DocListオブジェクトが返ってくるってことなのかな?
HTTPエンドポイントのドキュメントを見ると/
はヘルスチェック用で、デフォルトは/default
になっていた。で、gRPCと同じことをHTTPでやるにはこんな感じ?
$ curl -X POST http://192.XXX.XXX.XXX:54322/default -H "Content-type: application/json" -d '{"data":[{},{}]}' | jq
{
"data": [
{
"id": "774e828a96fb9a46a605b4e6e8b8459d",
"text": "hello, world!",
"url": null,
"embedding": null,
"bytes_": null
},
{
"id": "8f7c289319d9f1696f6659db6bcfb8b1",
"text": "goodbye, world!",
"url": null,
"embedding": null,
"bytes_": null
}
],
"parameters": {}
}
なんか近しい感じになったんじゃないだろうか。
ということで、コードを少し修正してみる。PyTorchで適当なテンソルを返す/crunch-numbers
エンドポイントを追加する。
まずPyTorchのインストール。
$ echo "torch" >> executor1/requirements.txt
$ pip install -r executor1/requirements.txt
executor1/executor.py
を以下のように修正
from jina import Executor, requests
from docarray import DocList
from docarray.documents import TextDoc
# 以下のimportを追加
import numpy as np
import torch
class MyExecutor(Executor):
@requests
def foo(self, docs: DocList[TextDoc], **kwargs) -> DocList[TextDoc]:
docs[0].text = 'hello, world!'
docs[1].text = 'goodbye, world!'
return docs
# 以下を追加
@requests(on='/crunch-numbers')
def bar(self, docs, **kwargs):
for doc in docs:
doc.tensor = torch.tensor(np.random.random([10, 2]))
client.py
で上記エンドポイントにアクセスするよう修正。
from jina import Client
from docarray import DocList
from docarray.documents import TextDoc
# 以下import文を追加
from docarray.documents.legacy import LegacyDocument
if __name__ == '__main__':
c = Client(host='grpc://0.0.0.0:54321')
# 以下を書き換え
da = c.post('/crunch-numbers', DocList[LegacyDocument]([LegacyDocument(), LegacyDocument()]), return_type=DocList[LegacyDocument])
print(da.tensor)
反映は自動で行われないので、DeploymentをCtrl+Cで止めて再度上げる。
そしてclient.py
でアクセスするとこうなる。
$ python client.py
[TorchTensor([[0.0782, 0.8126],
[0.8250, 0.6428],
[0.4835, 0.9296],
[0.5484, 0.3298],
[0.8940, 0.1521],
[0.7266, 0.4436],
[0.3097, 0.5111],
[0.1346, 0.8266],
[0.5417, 0.0750],
[0.8806, 0.0736]], dtype=torch.float64), TorchTensor([[0.7132, 0.4658],
[0.5118, 0.7671],
[0.1066, 0.8973],
[0.0187, 0.8175],
[0.8474, 0.2664],
[0.1936, 0.2980],
[0.9680, 0.6595],
[0.0110, 0.4229],
[0.7062, 0.9453],
[0.3600, 0.3236]], dtype=torch.float64)]
で作成したものはJina AI Cloudにデプロイできる。。。のだけど、現状デプロイできるのはFlowsのみで、Deploymentsはまだできない。
まだピンときてないのだけども、他のドキュメントを色々見てみた感じとしては、アプリケーションを作るためのフレームワークと言うよりは、
- モデルの実行環境に、gRPCのエンドポイントをつけて、デプロイするためのフレームワーク
- GPUを必要とするようなTransformerモデルや画像生成モデルなどをターゲットにしている
- アプリケーション(UI含む)は別に用意して、そこからアクセスしてつかう
って感じなのかな。Flowsを使えばおそらくワークフロー的に処理できるのだろうと思うので、ワークフローにシンプルな入出力インタフェースをつけるようなものだと感じた。確かにFastAPIと同じ位置付けというのは納得できる。
概念的なところはここに書いてある。
けど、ちょっと今の自分的にはあまり必要なさそうかなぁというところ。
あと、使いこなすにはDocArrayを理解している必要はありそう。
改めて、ドキュメント見てたのだけど、とりあえず動かしてみてまずは雰囲気を理解するならば、GitHubレポジトリのGet Startedのほうが良さそう。