ラベル ベクトル・データベース の投稿を表示しています。 すべての投稿を表示
ラベル ベクトル・データベース の投稿を表示しています。 すべての投稿を表示

2023年8月1日火曜日

Llama.cpp_serverのOpenAI互換APIとChromaを使ってIn-context Learningを行なう

 以前の記事で、Llama_cpp.serverのOpenAI互換APIとベクトル・データベースとしてPineconeを使ったIn-context Learningを行なうAPEXアプリケーションを作成しました。そのPineconeの部分をChromaに置き換えてみました。

ベクトル・データベースを置き換えただけなので、APEXアプリケーション自体にほとんど変更はありません。

置き換えたAPEXアプリケーションのエクスポートは以下になります。

https://github.com/ujnak/apexapps/blob/master/exports/vector-documents-search-chroma.zip

ChromaにAPEXからアクセスするためのパッケージCHROMA_APIを作成しています。JavaScript APIを参考にしていますが、実装を省略した部分は多々あります。(例えば引数としてstringまたはstring[]を受け取る部分をstring[]に限定していたりします)。

パッケージKB_LLM_UTILのPineconeの呼び出し部分を、パッケージCHROMA_APIのファンクションの呼び出しに置き換えています。

APEXアプリケーションの置換文字列のG_INDEXに、Chromaに作成したコレクションのIDを設定します。G_ENDPOINTとして、LLama_cpp.serverとChromaが稼働しているホストを指すURLを設定します。

パッケージCHROMA_APIにはコレクションを作成するファンクションCREATE_COLLECTIONまたはGET_OR_CREATE_COLLECTIONが含まれています。そのファンクションを呼び出して、Chromaのコレクションを作成します。

metadataのhnsw:spaceにcosineを指定し、ベクトル検索時にコサイン類似度を使うようにします。

作成されているコレクションを一覧するには、LIST_COLLECTIONSを呼び出します。

作成したコレクションを削除するには、DELETE_COLLECTIONを呼び出します。

以上が、Chromaに切り替えた変更点です。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

完

2023年7月31日月曜日

ベクトル・データベースのChromaをAmpere A1のインスタンスで実行する

 ベクトル・データベースのChromaをOracle CloudのAlways Free枠で作成したAmpere A1のインスタンス上で実行してみました。

Oracle APEXから呼び出して使用することを想定しているため、サーバーとして実行します。また、インストールする環境はこちらの記事で紹介しているllama_cpp.serverを実行している環境です。ひとつの環境で、Open AIの互換APIを提供するllama_cpp.serverの実行と、ベクトル類似度検索を行なうベクトル・データベースの両方を実装します。

llama_cpp.serverとChromaを同居させるため、Chromaはポート8080でネットワーク接続の待ち受けを行なうように設定します。

firewalldの設定で8080/tcpへの接続を許可します。

sudo firewall-cmd --add-port=8080/tcp --zone=public
sudo firewall-cmd --list-all --zone=public
sudo firewall-cmd --runtime-to-permanent

$ sudo firewall-cmd --add-port=8080/tcp --zone=public

success

$ sudo firewall-cmd --list-all --zone=public

public

  target: default

  icmp-block-inversion: no

  interfaces: 

  sources: 

  services: dhcpv6-client http https ssh

  ports: 8000/tcp 8080/tcp

  protocols: 

  masquerade: no

  forward-ports: 

  source-ports: 

  icmp-blocks: 

  rich rules: 

$ sudo firewall-cmd --runtime-to-permanent

success

$


docker-composeをインストールします。

sudo apt install docker-compose

docker-composeとdocker-composeの実行に必要な関連パッケージがインストールされます。ただし、docker-composeのバージョンが古く(1.25.0-1でした)、このままではChromaのサーバーを実行できません。

DockerのドキュメントのInstall Compose standaloneに記載されているコマンドを実行します。

sudo curl -SL https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-linux-aarch64 -o /usr/local/bin/docker-compose

$ sudo curl -SL https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-linux-aarch64 -o /usr/local/bin/docker-compose

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0

100 55.3M  100 55.3M    0     0   167M      0 --:--:-- --:--:-- --:--:--  167M

$ 


ダウンロードしたファイルに実行フラグを立てます。

sudo chmod a+x /usr/local/bin/docker-compose

$ sudo chmod a+x /usr/local/bin/docker-compose 

$ 


今回はARM上の実行なのでaarch64を選んでいます。バージョンも時期によって変わるため、以下のサイトにアクセスして、適切なファイルをダウンロードします。

https://github.com/docker/compose/releases/


ChromaのドキュメントのGetting Startedより、JavaScriptの手順を参照します。

Pythonと異なりJavaScriptのAPIはサーバーを含みません。そのため、Chromaをサーバーとして実行する手順が記載されています。

https://docs.trychroma.com/getting-started


/usr/local/binの下にあるdocker-composeが優先されるように、環境変数PATHを設定します。その後、docker-composeのバージョンを確認します。

export PATH=/usr/local/bin:$PATH
docker-compose -v


ubuntu@mywhisper2:~$ export PATH=/usr/local/bin:$PATH

ubuntu@mywhisper2:~$ docker-compose -v

Docker Compose version v2.20.2

ubuntu@mywhisper2:~$ 


GitHubよりChromaをクローンします。

git clone https://github.com/chroma-core/chroma.git

$ git clone https://github.com/chroma-core/chroma.git

Cloning into 'chroma'...

remote: Enumerating objects: 16034, done.

remote: Counting objects: 100% (1761/1761), done.

remote: Compressing objects: 100% (512/512), done.

remote: Total 16034 (delta 1386), reused 1486 (delta 1241), pack-reused 14273

Receiving objects: 100% (16034/16034), 173.16 MiB | 58.21 MiB/s, done.

Resolving deltas: 100% (10303/10303), done.

$


作成されたディレクトリchromaに移動します。

cd chroma

$ cd chroma

~/chroma$ ls 

DEVELOP.md          bin                                docker-compose.yml        pyproject.toml

Dockerfile          chromadb                           docs                      requirements.txt

LICENSE             clients                            examples                  requirements_dev.txt

README.md           docker-compose.server.example.yml  log_config.yml

RELEASE_PROCESS.md  docker-compose.test.yml            pull_request_template.md

~/chroma$ 


docker-compose.ymlを開き、portsの設定を8000:8000から8080:8000に変更します。

version: '3.9'


networks:

  net:

    driver: bridge


services:

  server:

    image: server

    build:

      context: .

      dockerfile: Dockerfile

    volumes:

      - ./:/chroma

      - index_data:/index_data

    command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml

    environment:

      - IS_PERSISTENT=TRUE

    ports:

      - 8080:8000

    networks:

      - net


volumes:

  index_data:

    driver: local

  backups:

    driver: local


docker-composeを実行し、Chomaのサーバーを起動します。

sudo docker-compose up -d --build

ubuntu@mywhisper2:~/chroma$ sudo docker-compose up -d --build

[+] Building 0.3s (15/16)                                                                                  

 => [server internal] load build definition from Dockerfile                                           0.0s

 => => transferring dockerfile: 771B                                                                  0.0s

 => [server internal] load .dockerignore                                                              0.0s

 => => transferring context: 131B                                                                     0.0s

 => [server internal] load metadata for docker.io/library/python:3.10-slim-bookworm                   0.2s

 => [server builder 1/6] FROM docker.io/library/python:3.10-slim-bookworm@sha256:84ecab4ecf38604b04b  0.0s

 => [server internal] load build context                                                              0.0s

 => => transferring context: 908.78kB                                                                 0.0s

 => CACHED [server builder 2/6] RUN apt-get update --fix-missing && apt-get install -y --fix-missing  0.0s

 => CACHED [server final 3/7] RUN mkdir /chroma                                                       0.0s

 => CACHED [server final 4/7] WORKDIR /chroma                                                         0.0s

 => CACHED [server builder 3/6] RUN mkdir /install                                                    0.0s

 => CACHED [server builder 4/6] WORKDIR /install                                                      0.0s

 => CACHED [server builder 5/6] COPY ./requirements.txt requirements.txt                              0.0s

 => CACHED [server builder 6/6] RUN pip install --no-cache-dir --upgrade --prefix="/install" -r requ  0.0s

 => CACHED [server final 5/7] COPY --from=builder /install /usr/local                                 0.0s

 => CACHED [server final 6/7] COPY ./bin/docker_entrypoint.sh /docker_entrypoint.sh                   0.0s

 => [server final 7/7] COPY ./ /chroma                                                                0.0s

 => [server] exporting to image                                                                       0.0s

 => => exporting layers                                                                               0.0s

 => => writing image sha256:be7dda545e17f0772d56a51ffe52739787818d75ad350a0125f20a8c5c602a61          0.0s

 => => naming to docker.io/library/server                                                             0.0s

[+] Running 2/2

 ✔ Network chroma_net         Created                                                                 0.2s 

 ✔ Container chroma-server-1  Started                                                                 0.4s 

ubuntu@mywhisper2:~/chroma$ 


Nginxがllama_cpp.serverとChromaの両方のプロキシを行なうように、/etc/nginx/conf.d/server.confを以下に置き換えます。


Nginxを再起動します。

sudo systemctl restart nginx

$ sudo systemctl restart nginx

$ 


以上でChromaが使えるようになりました。

動作の確認を行います。

手元のPCよりcurlコマンドを使ってAPIを呼び出します。URLや引数は正式な資料が見つからなかったため、JavaScriptのクライアント側のコードより推定しています。


バージョンを確認します。

curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/version

% curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/version

"0.4.3"



ハートビートを実行します。

curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/heartbeat

% curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/heartbeat

{"nanosecond heartbeat":1690775118527243421}


コレクション(インデックス)my_collectionを作成します。

curl -X POST -H 'Content-Type: application/json' -d '{ "name": "my_collection" }' https://ホスト名/api/v1/collections

% curl -X POST -H 'Content-Type: application/json' -d '{ "name": "my_collection" }' https://ホスト名/api/v1/collections

{"name":"my_collection","id":"ffaa6cfd-e9cd-4358-a9bd-8cd45774ae2b","metadata":null}


レスポンスに含まれるidは、ベクトル埋め込みを追加する際に使用します。

コレクションの一覧を取得します。

curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/collections

% curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/collections


[{"name":"my_collection","id":"ffaa6cfd-e9cd-4358-a9bd-8cd45774ae2b","metadata":null}]


コレクションにベクトル埋め込みを追加します。embeddings以外にmetadatasやdocumentsといった属性も含めることができます。

curl -X POST -H 'Content-Type: application/json' -d '{ "ids": [ "emb01","emb02" ], "embeddings": [[1,2,3],[4,5,6]] }' https://ホスト名/api/v1/collections/コレクションのID/add

% curl -X POST -H 'Content-Type: application/json' -d '{ "ids": [ "emb01","emb02" ], "embeddings": [[1,2,3],[4,5,6]] }' https://ホスト名/api/v1/collections/ffaa6cfd-e9cd-4358-a9bd-8cd45774ae2b/add

true


コレクションに含まれるベクトル埋め込みの数を取得します。

curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/collections/コレクションのID/count

% curl -X GET -H 'Content-Type: application/json' https://ホスト名/api/v1/collections/ffaa6cfd-e9cd-4358-a9bd-8cd45774ae2b/count

2


コレクションを検索します。

curl -X POST -H 'Content-Type: application/json' -d '{ "query_embeddings": [[1,2,4]] }' https://ホスト名/api/v1/collections/コレクションのID/query

% curl -X POST -H 'Content-Type: application/json' -d '{ "query_embeddings": [[1,2,4]] }' https://ホスト名/api/v1/collections/ffaa6cfd-e9cd-4358-a9bd-8cd45774ae2b/query

{"ids":[["emb01","emb02"]],"distances":[[1.0,22.0]],"metadatas":[[null,null]],"embeddings":null,"documents":[[null,null]]}


コレクションを削除します。

curl -X DELETE -H 'Content-Type: application/json' https://ホスト名/api/v1/collections/コレクション名

% curl -X DELETE -H 'Content-Type: application/json' https://ホスト名/api/v1/collections/my_collection                       


null


今回の作業は以上になります。ベクトル・データベースとして、一通りの操作ができそうです。

今後、今までPineconeを使っていたAPEXアプリケーションをオープンソースのChromaに置き換えてみようと考えています。

完

2023年7月25日火曜日

Llama.cpp_serverのOpenAI互換APIを呼び出してIn-context Learningを行なう

先日、Cohereをベクトル埋め込みと応答の生成に、Pineconeをベクトル類似度検索に使用したIn-context Learningを行なうAPEXアプリケーションを作成してみました。

このアプリケーションのCohereを使った処理を、Llama.cppのOpenAI互換APIの呼び出しに変えてみます。Llama.cppでOpenAI互換APIを使用する方法については、こちらの記事で紹介しています。

Llama2の7Bのモデルで生成されるベクトル埋め込みの次元は4096、13Bのモデルでは5120になります。Pineconeのインデックスは使用するモデルの次元数に合わせて作り直す必要があります。

改変したAPEXアプリケーションのエクスポートは以下です。
https://github.com/ujnak/apexapps/blob/master/exports/vector-documents-search-openai.zip

知識として以下の4行を登録します。
「上野動物園では、アジアゾウのウタイとアルンの母子とスーリヤの3頭が飼育されています。」
「多摩動物公園ではアヌーラ、アマラ、ビィドゥラの3頭のスリランカゾウとアフリカゾウのトムの1頭が飼育されています。」
「横浜動物園ズーラシアではラスクマルとシュリーの2頭のアジアゾウが飼育されています。」
「金沢動物園ではボンとヨーコの2頭のインドゾウが飼育されています。」

アプリケーションはファイルのアップロードだけではなく、知識を直接フォームから登録できるように改変しています。


アップロードするファイルもPDFではなく、セパレータとして'---'または'==='を含めたプレーンテキストの文書を受け付けるように変更しています。

質問を実行してみます。

質問:「金沢動物園で飼育されているゾウの種類はなんですか。」
回答:「おはようございます!金沢動物園で飼育されているのは、インドゾウです。」


APIのリクエストに含めるmessagesとして、roleがsystemであるcontentとroleがuserであるcontentを含めるようにしています。

roleがsystemのcontentとして、ページ・アイテムP3_PROMPT_SYSTEMに入力した文章をそのまま使用します。roleがuserのcontentとして、ページ・アイテムP3_PROMPT_TEMPLATEに記述されたテンプレートの{context_str}の部分をインデックスの検索結果として得られた知識(チャンク)で置き換え、そして、{query_str}の部分を質問で置き換えた文章を使用します。

ページ・アイテムP3_PROMPT_SYSTEMは、以下をデフォルトとして設定しています。

あなたは日本語を話すアシスタントです。

ページ・アイテムP3_PROMPT_TEMPLATEは、以下をデフォルトとして設定しています。

以下の情報が与えられています。 
---------------------
{context_str}
---------------------
この情報から、次の質問に答えてください。:  {query_str}

APEXのアプリケーションから質問を行うと、Llama.cppからは以下のログが出力されます。

llama_print_timings:        load time = 24694.52 ms

llama_print_timings:      sample time =     0.00 ms /     1 runs   (    0.00 ms per token,      inf tokens per second)

llama_print_timings: prompt eval time = 11806.55 ms /    34 tokens (  347.25 ms per token,     2.88 tokens per second)

llama_print_timings:        eval time =     0.00 ms /     1 runs   (    0.00 ms per token,      inf tokens per second)

llama_print_timings:       total time = 11895.69 ms

INFO:     ::1:39706 - "POST /v1/embeddings HTTP/1.0" 200 OK

Llama.generate: prefix-match hit


llama_print_timings:        load time = 24694.52 ms

llama_print_timings:      sample time =    40.99 ms /    41 runs   (    1.00 ms per token,  1000.22 tokens per second)

llama_print_timings: prompt eval time = 55983.91 ms /   162 tokens (  345.58 ms per token,     2.89 tokens per second)

llama_print_timings:        eval time = 15736.55 ms /    40 runs   (  393.41 ms per token,     2.54 tokens per second)

llama_print_timings:       total time = 72299.14 ms

INFO:     150.136.133.92:0 - "POST /v1/chat/completions HTTP/1.0" 200 OK


質問文のベクトル埋め込みの計算(POST /v1/embeddings)で、約12秒、回答の生成(POST /v1/chat/completions)に72秒程度の時間がかかっています。非常に簡単な質問でこの程度の処理時間なので、長文はほぼ扱えません。

次の質問です。

質問:「金沢動物園で飼育されているアジアゾウの名前を教えてください。」
回答:「はい、すみません。金沢動物園で飼育されているアジアゾウの名前は「ボン」と「ヨーコ」です。」

正解に見えます。

次の質問です。

質問:「多摩動物公園で飼育されているアジアゾウの名前を教えてください。」
回答:「はい、あなたに答えます。多摩動物公園で飼育されているアジアゾウの名前は、アヌーラです。」

多摩動物公園にはアヌーラの他にアマラとビィドゥラが飼育されていますが、必ずしも不正解とは言えないでしょう。

質問を変えてみます。「すべて」を追加しました。

質問:「多摩動物公園で飼育されているアジアゾウの名前をすべて教えてください。」
回答:「はい、あなたに答えます。以下のように、多摩動物公園で飼育されているアジアゾウの名前は、アヌーラ、アマラ、ビィドゥラ、トムです。」

回答にアフリカゾウのトムが含まれているため、不正解です。

以下の質問をしてみます。知識は不要なので、テンプレートを変更し{query_str}のみを記載します。

質問:「インドゾウとスリランカゾウとアフリカゾウのうち、アジアゾウに含まれるゾウはどれですか?」
回答:「アジアゾウに含まれるゾウは、インドゾウとスリランカゾウの2種類です。」

正解です。知識としてはあるのかもしれません。質問のテンプレートを以下に変更し、同じ質問をしてみます。

以下の情報が与えられています。 
---------------------
{context_str}
アジアゾウに含まれるゾウは、インドゾウとスリランカゾウの2種類です。アジアゾウにアフリカゾウは含まれません。
---------------------
この情報から、次の質問に日本語で答えてください。:  {query_str}

回答:「はい、あなたに答えます。以下が多摩動物公園で飼育されているアジアゾウの名前です。
* アヌーラ
* アマラ
* ビィドゥラ

私たちは、スリランカゾウとアフリカゾウを飼育していません。」

トムは除かれましたが、最後に正しくない回答が加わっています。

以下の質問は回答が間違っています。

質問:「アジアゾウのアルンが飼育されている動物園を教えてください。」
回答:「あなたは、多摩動物公園でアジアゾウのアルンが飼育されています。」

Pineconeのインデックスを検索したときにTop Kとして1を与えていて、以下の知識だけがプロンプトに含まれました。

「多摩動物公園ではアヌーラ、アマラ、ビィドゥラの3頭のスリランカゾウとアフリカゾウのトムの1頭が飼育されています。」

まったく知識がない状態なので、適当な回答が生成されています。

Top Kを2にし、2番目に類似度が高い知識もプロンプトに含めてみました。2番目の知識に上野動物園の情報が含まれたので、正解に見える回答が得られています。

回答:「はい、わかりました。アジアゾウのアルンは上野動物園で飼育されています。」

少し質問を変えてみます。Top Kに2を指定します。

質問:「アルンのお母さんのゾウの名前を教えてください。」
回答:「はい、わかりました。アルンのお母さんのゾウの名前はスーリヤです。」

間違っています。知識として与えている文章が認識できてないようです。知識の方を以下の文章に変更します。

「上野動物園では、アジアゾウのアルンとその母のウタイ、その他にスーリヤの3頭が飼育されています。」

同じ質問をします。

回答:「はい、わかりました。アルンのお母さんのゾウの名前は「ウタイ」です。」

正解です。

実用できるかどうかの判断は、OpenAIのAPIやLlama2でも70Bのモデルを使って行なう必要があると思いますが、なかなか一筋縄ではいかないように感じます。

以下より、以前のアプリケーションからの変更点を説明します。

表定義ですが、表KB_CHUNKSの列SPLIT_CHARをSPLIT_CHARSに変更し、VARCHAR2(1)からVARCHAR2(8)に変更しています。その他、長い文字列を保存する列の型をVARCHAR2(4000)からCLOBに変更しています。

OpenAI互換のAPIを呼び出すように改変したパッケージKB_LLM_UTILのコードは以下になります。


アプリケーション定義の置換のセクションで、G_INDEXにPineconeのインデックス、G_ENDPOINTにOpenAI互換APIを呼び出すエンドポイント(OpenAIであればhttps://api.openai.com)を設定します。


Pineconeのインデックス操作に使用するWeb資格証明と、(OpenAIのAPIを呼び出す場合は)OpenAIのAPIを呼び出す際に使用するWeb資格証明を作成しておきます。


パッケージKB_LLM_UTILのプロシージャで、Pineconeにアクセスするものには引数p_pinecode_cred、OpenAIのAPIアクセスを行なうものは引数p_cred_idを持っています。

それぞれパラメータを選択し、値に作成したWeb資格証明の静的IDを設定します。


roleがsystemのメッセージのデフォルトは、ページ・アイテムP3_PROMPT_SYSTEMに設定されています。


roleがuserのメッセージを作るテンプレートは、ページ・アイテムP3_PROMPT_TEMPLATEに設定されています。


Llama2の処理に時間がかかるため、デフォルトの状態だとNginxでタイムアウトが発生します。server.confを以下に変更し、プロキシ関連のタイムアウトを6分に延長します。


アプリケーションの変更点は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

完