Dify+Ollama+XinferenceでDeepSeek-R1を使ったRAGをオンプレマシンで試す

# はじめに
世間をざわつかせているDeepSeek-R1を試してみたかったのと、RAGをデプロイするくらいはやっておかないと世間に置いていかれる危機感がした。

* DifyでRAGを実装するのが簡単らしい
* Ollama経由でhuggingfaceのモデルをロードできる
* XinferenceでRerankが使えるようになる

前評判どおりならプライベート環境にサービス展開してもイケるかもしれない期待。。。

## モチベーション
* 職場のマニュアルと規約が大量に散らばっているので、蓄積してRAGで検索して楽したい（あくまで個人的な利用）。

# 手元の環境
* OS: Ubuntu 22
* CPU: AMD Ryzen
* GPU: NVIDIA RTX A6000(VRAM 48GB) x 2枚 ここまでのVRAMは不要
* Docker 27.5.0, Docker Compose 2.32.3

# docker-compose

公式のYAMLファイルにOllamaとXinferenceのコンテナを加えて起動するように加筆する。
公式のまま起動して、個別にOllamaとXinferenceのコンテナを立てると連携が面倒なので一括起動とする。

dify/docker/docker-compose.yamlをいじって下さい。

以下、servicesにollamaとxinferenceを挿入、volumesにollamaを挿入。
depends_onにollamaとxinferenceも追加。

```diff_yaml:docker-compose.yaml
services:
# API service
  api:
# ... (中略) ...
    depends_on:
      - db
      - redis
+      - ollama
+      - xinference
# ... (中略) ...
  worker:
# ... (中略) ...
    depends_on:
      - db
      - redis
+      - ollama
+      - xinference
# ... (中略) ...
  # Ollama service
  ollama:
    image: ollama/ollama
    container_name: ollama
    restart: always
    ports:
      - "11434:11434" #デフォルトのポート番号
    volumes:
      - ./volumes/ollama:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              capabilities: [gpu]
              device_ids: ["0"] # GPUの指定。1枚ならdevice_idsはコメントアウト

  # xinference
  xinference:
    image: xprobe/xinference:latest
    container_name: xinference
    restart: always
    ports:
      - "9997:9997" #デフォルトのポート番号
    volumes:
      - ./volumes/xinference/xinference:/root/.xinference
      - ./volumes/xinference/cache/huggingface:/root/.cache/huggingface
      - ./volumes/xinference/cache/modelscope:/root/.cache/modelscope
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              capabilities: [gpu]
              device_ids: ["1"] # GPUの指定。1枚ならdevice_idsはコメントアウト
    command: ['xinference-local', '-H', '0.0.0.0']

# ... (中略) ...
volumes:
+  ollama: # 不要かも
  oradata:
  dify_es01_data:
```

docker-compose.yamlのカレントディレクトリでコンテナ群を起動させる。
```shell
sudo docker compose up -d
```

# 動作設定
## Difyの初期化
NginxのおかげでWebサーバの80番、443番ポートが開いているので、ブラウザでローカルページ http://localhost/ を開き、管理者設定して入る。

![dify_login](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/20140945-03c6-7c8a-666a-4460a5a5f845.png)

## Ollamaでhuggingfaceからモデルをロード
GGUFファイルであればhuggingfaceからロードできるため、大抵のLLMが扱えます。
今回はcyberagentの日本語でファインチューニングしたQwenベースのDeepSeek-R1をGGUF形式にしたモデルを適用する。

Ollamaのコンテナ内で実行せねばならないので、ここはどうにかならないか。。。
ひとまず以下を実行してLLMのモデルをロードする。

[cyberagentの日本語DeepSeek-R1(Qwen)をGGUF化したモデル](https://huggingface.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-32B-Japanese-gguf)
```shell
docker exec -it ollama ollama run hf.co/mmnga/cyberagent-DeepSeek-R1-Distill-Qwen-32B-Japanese-gguf
```

ollamaのコンテナ内で次のように`ollama run hf.co/`に続けてロードしたいGGUF形式のモデルを指定するだけ。

```shell
ollama run hf.co/{ユーザー名}/{リポジトリ}
```

## XinferenceでEmbeddingとRerankerをロード
ブラウザでローカルページ http://localhost:9997/ を開きローカルのXinferenceのページを開く。
Xinferenceは英語モデルばかりなので、LLMはhuggingfaceにまかせて、embeddingとrerankerをXinferenceで用意する。

docker-compose.yamlでGPU1で処理させており、VRAMは4108MBを消費している様子。

RAG用にEMBEDDING MODELSで **jina-embeddings-v3** をロード
Rerank用にRERANK MODELSで **bge-reranker-v2-m3** をロード

![xinference](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/32ba462e-46fe-3693-50fd-f8a592fe76c6.png)

登録のModel UIDをDifyで認識させる際に同じUIDで渡すので、とりあえずモデル名と同じにしておく。
左下のロケットボタンでLaunch。

![xinference_submit](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/eba2c7f4-0e6f-4905-23c9-c5fd8fc136aa.png)

# DifyでLLM, Embedding, Rerankerを指定

Difyに入って、右上のアカウントから**設定**に入り、**モデルプロバイダー**を選択

## Ollama
* Model Name: 任意の名前
* Base URL: http://ollama:11434
    * http://サービス名:yamlで指定したポート番号

![ollama_model](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/f16eab51-298a-972f-52ea-c8a21848872e.png)

## Xorbits Inference(Xinference)

* Model Type
    * EMBEDDING MODELSの **jina-embeddings-v3** は**Text Embedding**
    * RERANK MODELSの **bge-reranker-v2-m3** は**Rerank**
* Model Name: 任意の名前
* Server url: http://xinference:9997/
* Model uid: Xinferenceでモデルをロードした際に付けたUID

![xinference_model](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/72496538-113a-921b-cb61-da6fdfb596dd.png)

それぞれ登録が成功すると次のようになる。

![models](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/dd956382-3890-bf97-2d9f-efbc4067d68e.png)

# RAG
RAGはまずナレッジデータベースに書類やテキストをEmbedding modelでベクトルとして取り込みます。
そしてユーザーが入力したテキストのベクトルと近い知識を引いて生成する仕組み。

## ナレッジのデータベースを作成

詳細は他のサイトが詳しいので譲るとして、上部メニューの**ナレッジ**から取り込みます。

![knowledge](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/bd5c5a42-3a6a-b20a-2191-60c3d9a5357f.png)

## RAGアプリ作成

1. 上部メニューのスタジオから、テンプレの**Knowledge Retreival + Chatbot**を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/e52eb034-8911-a55e-b569-9e146c385579.png)
1. **KNOWLEDGE RETRIEVAL**ブロックで作ったナレッジを選択し、Rerankモデルを指定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/b799aab9-0d68-c944-39e5-94dc6d9c01ac.png)
1. Rerankで精度を上げるために**ハイブリッド検索**とRerankerのモデルを指定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/feec1bc9-dbdc-f6a9-2224-d3ce7ae98cf5.png)
1. **LLM**ブロックでモデルをロードしたLLMを選択し、最後まで生成してもらうために**Num Predict**を`-2`に指定する。
![llm](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/b2040a54-2af7-74cb-45bf-0e5f27ea4c12.png)
1. 最初に与えるコンテキスト（プロンプト）は一般化されたものは無いので、プロンプトチューニングとして各自で調べて試して下さい。
掲載しているものはdifyがデフォルトで用意しているテンプレで、定番のロールを与えて、思考する条件を明記する感じです。
※ このままアプリを実行したらコンテキストが無いって文書が生成されたので、コンテキストを末尾にしたら直りました（バグ？）。
ナレッジの埋め込み設定と合わせてプロンプトの入れ方を考えないといけないのかな〜。**よきサイトがございましたら教えて下さい。**
![default_prompt](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/26643708-a5ce-a77d-4076-1c96eb97083f.png)
1. 右上の**公開する**から公開して**アプリを実行**で、もうRAGを試せます。簡単すぎて感動。

# 気になるVRAMの消費
定常状態は次のLLMに23GB弱とEmbed/Rerankに4GB強。
コスパのいいRTX 5090(32GB)で動かせる！？
Mac mini/studioのユニファイドメモリのほうがコスパいいけど、レスポンスはどうかな

![nvidia](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610236/6e1a3177-f947-28a2-d5fb-6880642132a9.png)

# さいごに
Dify+Ollama+Xinferenceでhuggingfaceに公開されている豊富なモデルからRAGを試せるようになりました。
API化できるみたいだし、他のアプリに出力だけインプリメントできそうw

# 参考文献
* [difyの公式github](https://github.com/langgenius/dify)
* [DifyとOllamaでオープンなRAG型チャットボットを構築してみた。](https://zenn.dev/yakumo3/articles/415f83ed3f7cb7)
* [DifyとXinferenceを使ってローカルのみでrerankありRAGを試す](https://qiita.com/orca13/items/93431d9d1e8c1129be69#xinference)
* [Ollama で Hugging Face Hub の GGUF をそのまま使ってみる](https://note.com/schroneko/n/n6a7c34f0a50c)