OpenVINO と LlamaIndex を使用して RAG システムを作成#

この Jupyter ノートブックは、ローカルへのインストール後にのみ起動できます。

GitHub

検索拡張生成 (RAG) は、多くの場合プライベートまたはリアルタイムのデータを追加して LLM 知識を拡張する手法です。LLM は幅広いトピックについて推論できますが、その知識はトレーニングを受けた特定の時点までの公開データに限定されます。プライベート・データやモデルのカットオフ日以降に導入されたデータについて推論できる AI アプリケーションを構築するには、モデルの知識に必要な情報を追加する必要があります。適切な情報を取得してモデルプロンプトに挿入するプロセスは、検索拡張生成 (RAG) と呼ばれます。

LlamaIndex は、LLM を使用してコンテキスト拡張型生成 AI アプリケーションを構築するフレームワークです。LlamaIndex では、LLM の使用方法に制限はありません。LLM は、オート・コンプリート、チャットボット、半自律エージェントなどとして使用できます。それだけで使いやすくなります。

このチュートリアルは次のステップで構成されます:

  • 前提条件のインストール

  • OpenVINO と Hugging Face Optimum の統合を使用して、パブリックソースからモデルをダウンロードして変換します。

  • NNCF を使用してモデルの重みを 4 ビットまたは 8 ビットのデータタイプに圧縮します

  • RAG チェーン・パイプラインを作成します

  • Q&A パイプラインの実行

この例では、カスタマイズされた RAG パイプラインは次のコンポーネントから順番に構成され、埋め込み、再ランク付け、LLM が OpenVINO とともにデプロイされ、推論パフォーマンスが最適化されます。

RAG

RAG#

目次:

必要条件#

必要な依存関係をインストールします

import os 

os.environ["GIT_CLONE_PROTECTION_ACTIVE"] = "false" 

%pip install -Uq pip 
%pip uninstall -q -y optimum optimum-intel 
%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu\ 
"llama-index" "faiss-cpu" "pymupdf" "llama-index-readers-file" "llama-index-vector-stores-faiss" "llama-index-llms-langchain" "llama-index-llms-openvino" "llama-index-embeddings-openvino" "llama-index-postprocessor-openvino-rerank" "transformers>=4.40" \ 
"git+https://github.com/huggingface/optimum-intel.git"\ 
"git+https://github.com/openvinotoolkit/nncf.git"\ 
"datasets"\ 
"accelerate"\ 
"gradio" \ 
"langchain" 
%pip install --pre -Uq openvino openvino-tokenizers[transformers] --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly
Note: you may need to restart the kernel to use updated packages. 
WARNING: Skipping optimum as it is not installed. 
WARNING: Skipping optimum-intel as it is not installed. 
Note: you may need to restart the kernel to use updated packages.
import os 
from pathlib import Path 
import requests 
import shutil 
import io 

# モデル構成を取得 

config_shared_path = Path("../../utils/llm_config.py") 
config_dst_path = Path("llm_config.py") 
text_example_en_path = Path("text_example_en.pdf") 
text_example_cn_path = Path("text_example_cn.pdf") 
text_example_en = 
"https://github.com/openvinotoolkit/openvino_notebooks/files/15039728/Platform.Brief_Intel.vPro.with.Intel.Core.Ultra_Final.pdf" 
text_example_cn = 
"https://github.com/openvinotoolkit/openvino_notebooks/files/15039713/Platform.Brief_Intel.vPro.with.Intel.Core.Ultra_Final_CH.pdf" 

if not config_dst_path.exists(): 
    if config_shared_path.exists(): 
        try: 
            os.symlink(config_shared_path, config_dst_path) 
        except Exception: 
            shutil.copy(config_shared_path, config_dst_path) 
    else: 
        r = 
requests.get(url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/llm_config.py") 
        with open("llm_config.py", "w", encoding="utf-8") as f: 
            f.write(r.text) 
elif not os.path.islink(config_dst_path): 
    print("LLM config will be updated") 
    if config_shared_path.exists(): 
        shutil.copy(config_shared_path, config_dst_path) 
    else: 
        r = 
requests.get(url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/llm_config.py") 
        with open("llm_config.py", "w", encoding="utf-8") as f: 
            f.write(r.text) 

if not text_example_en_path.exists(): 
    r = requests.get(url=text_example_en) 
    content = io.BytesIO(r.content) 
    with open("text_example_en.pdf", "wb") as f: 
        f.write(content.read()) 

if not text_example_cn_path.exists(): 
    r = requests.get(url=text_example_cn) 
    content = io.BytesIO(r.content) 
    with open("text_example_cn.pdf", "wb") as f: 
        f.write(content.read())
LLM config will be updated

推論用のモデルを選択#

このチュートリアルではさまざまなモデルがサポートされており、提供されたオプションから 1 つを選択して、オープンソース LLM ソリューションの品質を比較できます。

: 一部のモデルの変換には、ユーザーによる追加アクションが必要になる場合があり、変換には少なくとも 64GB RAM が必要です。

利用可能な埋め込みモデルのオプションは次のとおりです:

BGE 埋め込みは一般的な埋め込みモデルです。このモデルは RetroMAE で事前トレーニングされており、対照学習を使用して大規模なペアデータでトレーニングされています。

利用可能な再ランクモデルのオプションは次のとおりです:

  • bge-reranker-v2-m3

  • bge-reranker-large

  • bge-reranker-base クロスエンコーダーを使用したリランカーモデルは、入力ペアに対してフル・アテンションを実行します。これは、埋め込みモデル (つまり、バイエンコーダー) よりも正確ですが、埋め込みモデルよりも時間がかかります。埋め込みモデルによって返された上位 k 個のドキュメントを再ランク付けするのに使用できます。

利用可能な LLM モデルオプションは、llm-chatbot ノートブックでも確認できます。

from pathlib import Path 
import openvino as ov 
import ipywidgets as widgets

モデルを変換しモデルの重みを圧縮#

重み圧縮アルゴリズムは、モデルの重みを圧縮することを目的としており、大規模言語モデル (LLM) など、重みのサイズが活性化のサイズよりも相対的に大きい大規模モデルのモデル・フットプリントとパフォーマンスを最適化するために使用できます。INT8 圧縮と比較して、INT4 圧縮はパフォーマンスをさらに向上させますが、予測品質は若干低下します。

from llm_config import ( 
    SUPPORTED_EMBEDDING_MODELS, 
    SUPPORTED_RERANK_MODELS, 
    SUPPORTED_LLM_MODELS, 
) 

model_languages = list(SUPPORTED_LLM_MODELS) 

model_language = widgets.Dropdown( 
    options=model_languages, 
    value=model_languages[0], 
    description="Model Language:", 
    disabled=False, 
) 

model_language
Dropdown(description='Model Language:', options=('English', 'Chinese', 'Japanese'), value='English')
llm_model_ids = [model_id for model_id, model_config in SUPPORTED_LLM_MODELS[model_language.value].items() if model_config.get("rag_prompt_template")] 

llm_model_id = widgets.Dropdown( 
    options=llm_model_ids, 
    value=llm_model_ids[-1], 
    description="Model:", 
    disabled=False, 
) 

llm_model_id
Dropdown(description='Model:', index=12, options=('tiny-llama-1b-chat', 'gemma-2b-it', 'red-pajama-3b-chat', '…
llm_model_configuration = SUPPORTED_LLM_MODELS[model_language.value][llm_model_id.value] 
print(f"Selected LLM model {llm_model_id.value}")
Selected LLM model llama-3-8b-instruct

Optimum Intel は、インテル® アーキテクチャー上のエンドツーエンドのパイプラインを高速化する、トランスフォーマーおよび Diffuser ライブラリーと OpenVINO 間のインターフェイスです。モデルを OpenVINO 中間表現 (IR) 形式にエクスポートする使いやすい CLI インターフェイスを提供します。

以下は、optimum-cli を使用したモデル・エクスポートの基本コマンドを示しています。

optimum-cli export openvino --model <model_id_or_path> --task <task> <out_dir>

--model 引数は、HuggingFace Hub またはモデルのあるローカル・ディレクトリー (.save_pretrained メソッドを使用して保存) のモデル ID であり、--task は、エクスポートされたモデルが解決する必要があるサポートされているタスクの 1 つです。LLM の場合は、text-generation-with-past になります。モデルの初期化にリモートコードを使用する場合は、--trust-remote-code フラグを渡す必要があります。

Optimum-CLI を使用した LLM 変換と重み圧縮#

また、CLI を使用してモデルをエクスポートするときに、--weight-format をそれぞれ fp16、int8、int4 に設定することで、線形、畳み込み、埋め込みレイヤーに fp16、8 ビット、または 4 ビットの重み圧縮を適用することもできます。このタイプ最適化により、メモリー・フットプリントと推論の待ち時間を削減できます。デフォルトでは、int8/int4 の量子化スキームは非対称になりますが、対称に​​するには --sym を追加します。

INT4 量子化の場合、次の引数を指定することもできます:

  • --group-size パラメーターは量子化に使用するグループサイズを定義します。-1 の場合はカラムごとの量子化になります。

  • --ratio パラメーターは、4 ビットと 8 ビットの量子化の比率を制御します。例えば、0.8 は、レイヤーの 90% が int4 に圧縮され、残りが int8 精度に圧縮されることを意味します。

group_size と ratio の値が小さいほど、モデルのサイズと推論のレイテンシーが犠牲になりますが、通常は精度が向上します。

: dGPU 上の INT4/INT8 圧縮モデルではスピードアップされない可能性があります。

from IPython.display import Markdown, display 

prepare_int4_model = widgets.Checkbox( 
    value=True, 
    description="Prepare INT4 model", 
    disabled=False, 
) 
prepare_int8_model = widgets.Checkbox( 
    value=False, 
    description="Prepare INT8 model", 
    disabled=False, 
) 
prepare_fp16_model = widgets.Checkbox( 
    value=False, 
    description="Prepare FP16 model", 
    disabled=False, 
) 

display(prepare_int4_model) 
display(prepare_int8_model) 
display(prepare_fp16_model)
Checkbox(value=True, description='Prepare INT4 model')
Checkbox(value=False, description='Prepare INT8 model')
Checkbox(value=False, description='Prepare FP16 model')

AWQ を使用した重み圧縮#

活性化認識重み量子化 (AWQ) は、モデルの重みを調整して INT4 圧縮の精度を高めるアルゴリズムです。圧縮された LLM の生成品質はわずかに向上しますが、キャリブレーション・データセットの重みを調整するのにかなりの時間が必要になります。キャリブレーションには、Wikitext データセットの wikitext-2-raw-v1/train サブセットを使用します。

以下では、INT4 精度でモデルのエクスポート中に AWQ を適用できるようにします。

: AWQ を適用するには、かなりのメモリーと時間が必要です。

: AWQ を適用するモデルに一致するパターンが存在しない可能性があり、その場合はスキップされます。

enable_awq = widgets.Checkbox( 
    value=False, 
    description="Enable AWQ", 
    disabled=not prepare_int4_model.value, 
) 
display(enable_awq)
Checkbox(value=False, description='Enable AWQ')
pt_model_id = llm_model_configuration["model_id"] 
pt_model_name = llm_model_id.value.split("-")[0] 
fp16_model_dir = Path(llm_model_id.value) / "FP16" 
int8_model_dir = Path(llm_model_id.value) / "INT8_compressed_weights" 
int4_model_dir = Path(llm_model_id.value) / "INT4_compressed_weights" 

def convert_to_fp16(): 
    if (fp16_model_dir / "openvino_model.xml").exists(): 
        return 
    remote_code = llm_model_configuration.get("remote_code", False) 
    export_command_base = "optimum-cli export openvino --model {} --task text-generation-with-past --weight-format fp16".format(pt_model_id) 
    if remote_code: 
        export_command_base += " --trust-remote-code" 
    export_command = export_command_base + " " + str(fp16_model_dir) 
    display(Markdown("**Export command:**")) 
    display(Markdown(f"`{export_command}`")) 
    ! $export_command 

def convert_to_int8(): 
    if (int8_model_dir / "openvino_model.xml").exists(): 
        return 
    int8_model_dir.mkdir(parents=True, exist_ok=True) 
    remote_code = llm_model_configuration.get("remote_code", False) 
    export_command_base = "optimum-cli export openvino --model {} --task text-generation-with-past --weight-format int8".format(pt_model_id) 
    if remote_code: 
        export_command_base += " --trust-remote-code" 
    export_command = export_command_base + " " + str(int8_model_dir) 
    display(Markdown("**Export command:**")) 
    display(Markdown(f"`{export_command}`")) 
    ! $export_command 

def convert_to_int4(): 
    compression_configs = { 
        "zephyr-7b-beta": { 
            "sym": True, 
            "group_size": 64, 
            "ratio": 0.6, 
        }, 
        "mistral-7b": { 
            "sym": True, 
            "group_size": 64, 
            "ratio": 0.6, 
        }, 
        "minicpm-2b-dpo": { 
            "sym": True, 
            "group_size": 64, 
            "ratio": 0.6, 
        }, 
        "gemma-2b-it": { 
            "sym": True, 
            "group_size": 64, 
            "ratio": 0.6, 
        }, 
        "notus-7b-v1": { 
            "sym": True, 
            "group_size": 64, 
            "ratio": 0.6, 
        }, 
        "neural-chat-7b-v3-1": { 
            "sym": True, 
            "group_size": 64, 
            "ratio": 0.6, 
        }, 
        "llama-2-chat-7b": { 
            "sym": True, 
            "group_size": 128, 
            "ratio": 0.8, 
        }, 
        "llama-3-8b-instruct": { 
            "sym": True, 
            "group_size": 128, 
            "ratio": 0.8, 
        }, 
        "gemma-7b-it": { 
            "sym": True, 
            "group_size": 128, 
            "ratio": 0.8, 
        }, 
        "chatglm2-6b": { 
            "sym": True, 
            "group_size": 128, 
            "ratio": 0.72, 
        }, 
        "qwen-7b-chat": {"sym": True, "group_size": 128, "ratio": 0.6}, 
        "red-pajama-3b-chat": { 
            "sym": False, 
            "group_size": 128, 
            "ratio": 0.5, 
        }, 
        "default": { 
            "sym": False, 
            "group_size": 128, 
            "ratio": 0.8, 
        }, 
    } 

    model_compression_params = compression_configs.get(llm_model_id.value, compression_configs["default"]) 
    if (int4_model_dir / "openvino_model.xml").exists(): 
        return 
    remote_code = llm_model_configuration.get("remote_code", False) 
    export_command_base = "optimum-cli export openvino --model {} --task text-generation-with-past --weight-format int4".format(pt_model_id) 
    int4_compression_args = " --group-size {} --ratio {}".format(model_compression_params["group_size"], model_compression_params["ratio"]) 
    if model_compression_params["sym"]: 
        int4_compression_args += " --sym" 
    if enable_awq.value: 
        int4_compression_args += " --awq --dataset wikitext2 --num-samples 128" 
    export_command_base += int4_compression_args 
    if remote_code: 
        export_command_base += " --trust-remote-code" 
    export_command = export_command_base + " " + str(int4_model_dir) 
    display(Markdown("**Export command:**")) 
    display(Markdown(f"`{export_command}`")) 
    ! $export_command 

if prepare_fp16_model.value: 
    convert_to_fp16() 
if prepare_int8_model.value: 
    convert_to_int8() 
if prepare_int4_model.value: 
    convert_to_int4()

さまざまな圧縮タイプのモデルサイズを比較してみましょう

fp16_weights = fp16_model_dir / "openvino_model.bin" 
int8_weights = int8_model_dir / "openvino_model.bin" 
int4_weights = int4_model_dir / "openvino_model.bin" 

if fp16_weights.exists(): 
    print(f"Size of FP16 model is {fp16_weights.stat().st_size / 1024 / 1024:.2f} MB") 
for precision, compressed_weights in zip([8, 4], [int8_weights, int4_weights]): 
    if compressed_weights.exists(): 
        print(f"Size of model with INT{precision} compressed weights is {compressed_weights.stat().st_size / 1024 / 1024:.2f} MB") 
    if compressed_weights.exists() and fp16_weights.exists(): 
        print(f"Compression rate for INT{precision} model: {fp16_weights.stat().st_size / compressed_weights.stat().st_size:.3f}")
Size of model with INT4 compressed weights is 5085.79 MB

Optimum-CLI を使用して埋め込みモデルを変換#

一部の埋め込みモデルは限られた言語しかサポートできないため、選択した LLM に応じてそれらを除外できます。

embedding_model_id = list(SUPPORTED_EMBEDDING_MODELS[model_language.value]) 

embedding_model_id = widgets.Dropdown( 
    options=embedding_model_id, 
    value=embedding_model_id[0], 
    description="Embedding Model:", 
    disabled=False, 
) 

embedding_model_id
Dropdown(description='Embedding Model:', options=('bge-small-en-v1.5', 'bge-large-en-v1.5', 'bge-m3'), value='…
embedding_model_configuration = SUPPORTED_EMBEDDING_MODELS[model_language.value][embedding_model_id.value] 
print(f"Selected {embedding_model_id.value} model")
Selected bge-small-en-v1.5 model

OpenVINO 埋め込みモデルとトークナイザーは、optimum-cli を使用した feature-extraction タスクによってエクスポートできます。

export_command_base = "optimum-cli export openvino --model {} --task feature-extraction".format(embedding_model_configuration["model_id"]) 
export_command = export_command_base + " " + str(embedding_model_id.value) 

if not Path(embedding_model_id.value).exists():
    ! $export_command

Optimum-CLI を使用して再ランクモデルを変換#

rerank_model_id = list(SUPPORTED_RERANK_MODELS) 

rerank_model_id = widgets.Dropdown( 
    options=rerank_model_id, 
    value=rerank_model_id[0], 
    description="Rerank Model:", 
    disabled=False, 
) 

rerank_model_id
Dropdown(description='Rerank Model:', options=('bge-reranker-v2-m3', 'bge-reranker-large', 'bge-reranker-base'…
rerank_model_configuration = SUPPORTED_RERANK_MODELS[rerank_model_id.value] 
print(f"Selected {rerank_model_id.value} model")
Selected bge-reranker-v2-m3 model

rerank モデルは一種の文分類タスクであるため、 OpenVINO IR とトークナイザーは、optimum-cli を使用した text-classification タスクによってエクスポートできます。

export_command_base = "optimum-cli export openvino --model {} --task text-classification".format(rerank_model_configuration["model_id"]) 
export_command = export_command_base + " " + str(rerank_model_id.value) 

if not Path(rerank_model_id.value).exists(): 
    ! $export_command

推論用のデバイスとモデルバリアントを選択#

: dGPU 上の INT4/INT8 圧縮モデルではスピードアップされない可能性があります。

モデル推論を埋め込むデバイスを選択#

core = ov.Core() 

support_devices = core.available_devices 

embedding_device = widgets.Dropdown( 
    options=support_devices + ["AUTO"], 
    value="CPU", 
    description="Device:", 
    disabled=False, 
) 

embedding_device
Dropdown(description='Device:', options=('CPU', 'AUTO'), value='CPU')
print(f"Embedding model will be loaded to {embedding_device.value} device for text embedding")
Embedding model will be loaded to CPU device for text embedding

再ランクモデル推論用のデバイスを選択#

rerank_device = widgets.Dropdown( 
    options=support_devices + ["AUTO"], 
    value="CPU", 
    description="Device:", 
    disabled=False, 
) 

rerank_device
Dropdown(description='Device:', options=('CPU', 'AUTO'), value='CPU')
print(f"Rerenk model will be loaded to {rerank_device.value} device for text reranking")
Rerenk model will be loaded to CPU device for text reranking

LLM モデル推論用のデバイスを選択#

llm_device = widgets.Dropdown( 
    options=support_devices + ["AUTO"], 
    value="CPU", 
    description="Device:", 
    disabled=False, 
) 

llm_device
Dropdown(description='Device:', options=('CPU', 'AUTO'), value='CPU')
print(f"LLM model will be loaded to {llm_device.value} device for response generation")
LLM model will be loaded to CPU device for response generation

モデルのロード#

埋め込みモデルのロード#

現在、Hugging Face 埋め込みモデルは、LlamaIndex の OpenVINOEmbeddings クラスを通じて OpenVINO でサポートできます。

from llama_index.embeddings.huggingface_openvino import OpenVINOEmbedding 

embedding = OpenVINOEmbedding(folder_name=embedding_model_id.value, device=embedding_device.value) 

embeddings = embedding.get_text_embedding("Hello World!") 
print(len(embeddings)) 
print(embeddings[:5])
Compiling the model to CPU ...
384 
[-0.003275666618719697, -0.01169075071811676, 0.04155930131673813, -0.03814813867211342, 0.02418304793536663]

再ランクモデルのロード#

現在、Hugging Face 埋め込みモデルは、LlamaIndex の OpenVINORerank クラスを通じて OpenVINO でサポートできます。

: RAG では再ランク付けをスキップできます。

from llama_index.postprocessor.openvino_rerank import OpenVINORerank 

reranker = OpenVINORerank(model=rerank_model_id.value, device=rerank_device.value, top_n=2)
Compiling the model to CPU ...

LLM モデルのロード#

OpenVINO モデルは、 HuggingFacePipeline クラスを通じてローカルで実行できます。OpenVINO を使用してモデルをデプロイするには、backend="openvino" パラメーターを指定し、バックエンド推論フレームワークとして OpenVINO をトリガーします。

available_models = [] 
if int4_model_dir.exists(): 
    available_models.append("INT4") 
if int8_model_dir.exists(): 
    available_models.append("INT8") 
if fp16_model_dir.exists(): 
    available_models.append("FP16") 

model_to_run = widgets.Dropdown( 
    options=available_models, 
    value=available_models[0], 
    description="Model to run:", 
    disabled=False, 
) 

model_to_run
Dropdown(description='Model to run:', options=('INT4',), value='INT4')

OpenVINO モデルは、LlamaIndexOpenVINOLLM クラスを通じてローカルで実行できます。インテル® GPU の場合は、device_map="gpu" を指定して推論を実行できます。

from llama_index.llms.openvino import OpenVINOLLM 

if model_to_run.value == "INT4": 
    model_dir = int4_model_dir 
elif model_to_run.value == "INT8": 
    model_dir = int8_model_dir 
else: 
    model_dir = fp16_model_dir 
print(f"Loading model from {model_dir}") 

ov_config = {"PERFORMANCE_HINT": "LATENCY", "NUM_STREAMS": "1", "CACHE_DIR": ""} 

if "GPU" in llm_device.value and "qwen2-7b-instruct" in llm_model_id.value: 
    ov_config["GPU_ENABLE_SDPA_OPTIMIZATION"] = "NO" 

# GPU デバイスでは、モデルは FP16 精度で実行されます。red-pajama-3b-chat モデルでは、これが原因で精度の問題が発生することが知られていますが、 
# 精度ヒントを "f32" に設定することでこれを回避できます 
if llm_model_id.value == "red-pajama-3b-chat" and "GPU" in core.available_devices and llm_device.value in ["GPU", "AUTO"]: 
    ov_config["INFERENCE_PRECISION_HINT"] = "f32」 

llm = OpenVINOLLM( 
    model_name=str(model_dir), 
    tokenizer_name=str(model_dir), 
    context_window=3900, 
    max_new_tokens=2, 
    model_kwargs={"ov_config": ov_config, "trust_remote_code": True}, 
    generate_kwargs={"temperature": 0.7, "top_k": 50, "top_p": 0.95}, 
    device_map=llm_device.value, 
) 

response = llm.complete("2 + 2 =") 
print(str(response))
The argument trust_remote_code is to be used along with export=True.It will be ignored.
Loading model from llama-3-8b-instruct/INT4_compressed_weights
Compiling the model to CPU ...
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained. 
Setting pad_token_id to eos_token_id:128001 for open-end generation.
4

ドキュメントの QA を実行#

一般的な RAG アプリケーションには、次の 2 つの主要コンポーネントがあります:

  • インデックス作成: ソースからデータを取り込み、インデックスを作成するパイプライン。これは通常、オフラインで発生します。

  • 取得と生成: 実際の RAG チェーンは、実行時にユーザークエリーを受け取り、インデックスから関連データを取得して、それをモデルに渡します。

生データから回答までの最も一般的な完全なシーケンスは次のようになります:

インデックス作成

  1. Load: まずデータをロードする必要があります。これには DocumentLoaders を使用します。

  2. Split: テキスト分割ツールは、大きなドキュメントを小さなチャンクに分割します。これは、データのインデックス作成とモデルへのデータの受け渡しの両方に役立ちます。大きなチャンクは検索が困難であり、モデルの有限コンテキスト・ウィンドウでは検索できないためです。

  3. Store: 後で検索できるように、分割を保存してインデックスを作成する場所が必要です。これは、多くの場合、VectorStore と Embeddings モデルを使用して行われます。

インデックス・パイプライン

インデックス・パイプライン#

from llama_index.core import VectorStoreIndex, StorageContext 
from llama_index.core.node_parser import SentenceSplitter 
from llama_index.core import Settings 
from llama_index.readers.file import PyMuPDFReader 
from llama_index.vector_stores.faiss import FaissVectorStore 
from transformers import StoppingCriteria, StoppingCriteriaList 
import faiss 
import torch 

if model_language.value == "English": 
    text_example_path = "text_example_en.pdf" 
else: 
    text_example_path = "text_example_cn.pdf" 
stop_tokens = llm_model_configuration.get("stop_tokens") 

class StopOnTokens(StoppingCriteria): 
    def __init__(self, token_ids): 
        self.token_ids = token_ids 

    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool: 
        for stop_id in self.token_ids: 
            if input_ids[0][-1] == stop_id: 
                return True 
        return False 

if stop_tokens is not None: 
    if isinstance(stop_tokens[0], str): 
        stop_tokens = llm._tokenizer.convert_tokens_to_ids(stop_tokens) 
    stop_tokens = [StopOnTokens(stop_tokens)] 

loader = PyMuPDFReader() 
documents = loader.load(file_path=text_example_path) 

# 埋め込みモデルの次元 
d = embedding._model.request.outputs[0].get_partial_shape()[2].get_length() 
faiss_index = faiss.IndexFlatL2(d) 
Settings.embed_model = embedding 

llm.max_new_tokens = 2048 
if stop_tokens is not None: 
    llm._stopping_criteria = StoppingCriteriaList(stop_tokens) 
Settings.llm = llm 

vector_store = FaissVectorStore(faiss_index=faiss_index) 
storage_context = StorageContext.from_defaults(vector_store=vector_store) 
index = VectorStoreIndex.from_documents( 
    documents, 
    storage_context=storage_context, 
    transformations=[SentenceSplitter(chunk_size=200, chunk_overlap=40)], 
)

取得と生成

  1. Retrieve: ユーザー入力があれば、Retriever を使用して関連する分割がストレージから取得されます。

  2. Generate: LLM は、質問と取得したデータを含むプロンプトを使用して回答を生成します。

検索と生成パイプライン

検索と生成パイプライン#

query_engine = index.as_query_engine(streaming=True, similarity_top_k=10, node_postprocessors=[reranker]) 
if model_language.value == "English": 
    query = "What can Intel vPro® Enterprise systems offer?" 
else: 
    query = "英特尔博锐® Enterprise系统提供哪些功能?" 

streaming_response = query_engine.query(query) 
streaming_response.print_response_stream()
Setting pad_token_id to eos_token_id:128001 for open-end generation.
 提供されたコンテキスト情報によると、インテル® vPro Enterprise システムは以下を提供できます: - 動的信頼のルート
- システム管理モード (SMM) 保護
- マルチキーサポートによるメモリー暗号化
- OS カーネル保護
- リモート KVM 制御による帯域外管理
- 一意のデバイス ID
- デバイス履歴
- 帯域内管理プラグイン
この情報は提供されたコンテキストのみに基づいており、外部の知識や理解を表すものではないことに注意してください。回答は、与えられたテキストで提示された内容を正確に反映することを目的としています。

Gradio デモ#

モデルが作成されたら、Gradio を使用してチャットボット・インターフェイスをセットアップできます。

まず、LlamaIndex パイプラインのデフォルトのプロンプト・テンプレートを確認します。

prompts_dict = query_engine.get_prompts() 

def display_prompt_dict(prompts_dict): 
    for k, p in prompts_dict.items(): 
        text_md = f"**Prompt Key**: {k}<br>" f"**Text:** <br>" 
        display(Markdown(text_md)) 
        print(p.get_template()) 
        display(Markdown("<br><br>")) 

display_prompt_dict(prompts_dict)

プロンプトキー: response_synthesizer:text_qa_templateText:


    Context information is below.
    --------------------- 
    {context_str} 
    --------------------- 
    Given the context information and not prior knowledge, answer the query.
    Query: {query_str} 
    Answer:

プロンプトキー: response_synthesizer:refine_templateText:

   The original query is as follows: {query_str} 
    We have provided an existing answer: {existing_answer} 
    We have the opportunity to refine the existing answer (only if needed) with some more context below.
    ------------ 
    {context_msg} 
    ------------ 
    Given the new context, refine the original answer to better answer the query. If the context isn't useful, return the original answer.
    Refined Answer:
from langchain.text_splitter import RecursiveCharacterTextSplitter 
from llama_index.core.node_parser import LangchainNodeParser 
import gradio as gr 

TEXT_SPLITERS = { 
    "SentenceSplitter": SentenceSplitter, 
    "RecursiveCharacter": RecursiveCharacterTextSplitter, 
} 

chinese_examples = [ 
    ["英特尔®酷睿™ Ultra处理器可以降低多少功耗?"], 
    ["相比英特尔之前的移动处理器产品,英特尔®酷睿™ Ultra处理器的AI推理性能提升了多少?"], 
    ["英特尔博锐® Enterprise系统提供哪些功能?"], 
] 

english_examples = [ 
    ["How much power consumption can Intel® Core™ Ultra Processors help save?"], 
    ["Compared to Intel’s previous mobile processor, what is the advantage of Intel® Core™ Ultra Processors for Artificial Intelligence?"], 
    ["What can Intel vPro® Enterprise systems offer?"], 
] 

examples = chinese_examples if (model_language.value == "Chinese") else english_examples 

def default_partial_text_processor(partial_text: str, new_text: str): 
    """ 
    helper for updating partially generated answer, used by default 

    Params: 
        partial_text: text buffer for storing previosly generated text
        new_text: text update for the current step 
    Returns: 
        updated text string 

    """ 
    partial_text += new_text 
    return partial_text 

text_processor = llm_model_configuration.get("partial_text_processor", default_partial_text_processor) 

def create_vectordb(doc, spliter_name, chunk_size, chunk_overlap, vector_search_top_k, vector_rerank_top_n, run_rerank): 
    """ 
    Initialize a vector database 

    Params: 
        doc: orignal documents provided by user 
        chunk_size: size of a single sentence chunk 
        chunk_overlap: overlap size between 2 chunks 
        vector_search_top_k: Vector search top k 
        vector_rerank_top_n: Rerrank top n 
        run_rerank: whether to run reranker 

    """ 
    global query_engine 
    global index 

    if vector_rerank_top_n > vector_search_top_k: 
        gr.Warning("Search top k must >= Rerank top n") 

    loader = PyMuPDFReader() 
    documents = loader.load(file_path=doc.name) 
    spliter = TEXT_SPLITERS[spliter_name](chunk_size=chunk_size, chunk_overlap=chunk_overlap) 
    if spliter_name == "RecursiveCharacter": 
        spliter = LangchainNodeParser(spliter) 
    faiss_index = faiss.IndexFlatL2(d) 
    vector_store = FaissVectorStore(faiss_index=faiss_index) 
    storage_context = StorageContext.from_defaults(vector_store=vector_store) 

    index = VectorStoreIndex.from_documents( 
        documents, 
        storage_context=storage_context, 
        transformations=[spliter], 
    ) 
    if run_rerank: 
        reranker.top_n = vector_rerank_top_n 
        query_engine = index.as_query_engine(streaming=True, similarity_top_k=vector_search_top_k, node_postprocessors=[reranker]) 
    else: 
        query_engine = index.as_query_engine(streaming=True, similarity_top_k=vector_search_top_k) 

    return "Vector database is Ready" 

def update_retriever(vector_search_top_k, vector_rerank_top_n, run_rerank): 
    """ 
    Update retriever 

    Params: 
        vector_search_top_k: size of searching results 
        vector_rerank_top_n: size of rerank results 
        run_rerank: whether run rerank step
 
    """ 
    global query_engine 
    global index 

    if vector_rerank_top_n > vector_search_top_k: 
        gr.Warning("Search top k must >= Rerank top n") 

    if run_rerank: 
        reranker.top_n = vector_rerank_top_n 
        query_engine = index.as_query_engine(streaming=True, similarity_top_k=vector_search_top_k, node_postprocessors=[reranker]) 
    else: 
        query_engine = index.as_query_engine(streaming=True, similarity_top_k=vector_search_top_k) 

def user(message, history): 
    """ 
    callback function for updating user messages in interface on submit button click 

    Params: 
        message: current message 
        history: conversation history 
    Returns:
        None 
    """ 
    # ユーザーのメッセージを会話履歴に追加 
    return "", history + [[message, ""]] 

def bot(history, temperature, top_p, top_k, repetition_penalty, do_rag): 
    """ 
    callback function for running chatbot on submit button click 

    Params: 
        history: conversation history 
        temperature: parameter for control the level of creativity in AI-generated text.
                 By adjusting the `temperature`, you can influence the AI model's probability distribution, making the text more focused or diverse. 
        top_p: parameter for control the range of tokens considered by the AI model based on their cumulative probability. 
        top_k: parameter for control the range of tokens considered by the AI model based on their cumulative probability, selecting number of tokens with highest probability. 
        repetition_penalty: parameter for penalizing tokens based on how frequently they occur in the text. 
        do_rag: whether do RAG when generating texts.
     """ 
    llm.generate_kwargs = dict( 
        temperature=temperature, 
        do_sample=temperature > 0.0, 
        top_p=top_p, 
        top_k=top_k, 
        repetition_penalty=repetition_penalty, 
    ) 

    partial_text = "" 
    if do_rag: 
        streaming_response = query_engine.query(history[-1][0]) 
        for new_text in streaming_response.response_gen: 
            partial_text = text_processor(partial_text, new_text) 
            history[-1][1] = partial_text 
            yield history 
    else: 
        streaming_response = llm.stream_complete(history[-1][0]) 
        for new_text in streaming_response: 
            partial_text = text_processor(partial_text, new_text.delta) 
            history[-1][1] = partial_text 
            yield history 

def request_cancel(): 
    llm._model.request.cancel() 

def clear_files(): 
    return "Vector Store is Not ready" 

with gr.Blocks( 
    theme=gr.themes.Soft(), 
    css=".disclaimer {font-variant-caps: all-small-caps;}", 
) as demo: 
    gr.Markdown("""<h1><center>QA over Document</center></h1>""") 
    gr.Markdown(f"""<center>Powered by OpenVINO and {llm_model_id.value} </center>""") 
    with gr.Row(): 
        with gr.Column(scale=1): 
            docs = gr.File( 
                label="Step 1: Load a PDF file", 
                value=text_example_path, 
                file_types=[ 
                    ".pdf", 
                ], 
            ) 
            load_docs = gr.Button("Step 2: Build Vector Store", variant="primary") 
            db_argument = gr.Accordion("Vector Store Configuration", open=False) 
            with db_argument: 
                spliter = gr.Dropdown( 
                    ["SentenceSplitter", "RecursiveCharacter"], 
                    value="SentenceSplitter", 
                    label="Text Spliter", 
                    info="Method used to splite the documents", 
                    multiselect=False, 
                ) 

                chunk_size = gr.Slider( 
                    label="Chunk size", 
                    value=200, 
                    minimum=50, 
                    maximum=2000, 
                    step=50, 
                    interactive=True, 
                    info="Size of sentence chunk", 
                ) 

                chunk_overlap = gr.Slider( 
                    label="Chunk overlap", 
                    value=20, 
                    minimum=0, 
                    maximum=400, 
                    step=10, 
                    interactive=True, 
                    info=("Overlap between 2 chunks"), 
                ) 

            vector_store_status = gr.Textbox( 
                label="Vector Store Status", 
                value="Vector Store is Ready", 
                interactive=False, 
            ) 
            do_rag = gr.Checkbox( 
                value=True, 
                label="RAG is ON", 
                interactive=True, 
                info="Whether to do RAG for generation", 
            ) 
            with gr.Accordion("Generation Configuration", open=False): 
                with gr.Row(): 
                    with gr.Column(): 
                        with gr.Row(): 
                            temperature = gr.Slider( 
                                label="Temperature", 
                                value=0.1, 
                                minimum=0.0, 
                                maximum=1.0, 
                                step=0.1, 
                                interactive=True, 
                                info="Higher values produce more diverse outputs", 
                            ) 
                    with gr.Column(): 
                        with gr.Row(): 
                            top_p = gr.Slider( 
                                label="Top-p (nucleus sampling)", 
                                value=1.0, 
                                minimum=0.0, 
                                maximum=1, 
                                step=0.01, 
                                interactive=True, 
                                info=( 
                                    "Sample from the smallest possible set of tokens whose cumulative probability " 
                                    "exceeds top_p.Set to 1 to disable and sample from all tokens."                                 ), 
                            ) 
                with gr.Column(): 
                    with gr.Row(): 
                        top_k = gr.Slider( 
                            label="Top-k", 
                            value=50, 
                            minimum=0.0, 
                            maximum=200, 
                            step=1, 
                            interactive=True, 
                            info="Sample from a shortlist of top-k tokens — 0 to disable and sample from all tokens.", 
                        ) 
                with gr.Column(): 
                    with gr.Row(): 
                        repetition_penalty = gr.Slider( 
                            label="Repetition Penalty", 
                            value=1.1, 
                            minimum=1.0, 
                            maximum=2.0, 
                            step=0.1, 
                            interactive=True, 
                            info="Penalize repetition — 1.0 to disable.", 
                        ) 
        with gr.Column(scale=4): 
            chatbot = gr.Chatbot( 
                height=600, 
                label="Step 3: Input Query", 
            ) 
            with gr.Row(): 
                with gr.Column(): 
                    with gr.Row(): 
                        msg = gr.Textbox( 
                            label="QA Message Box", 
                            placeholder="Chat Message Box", 
                            show_label=False, 
                            container=False, 
                        ) 
                with gr.Column(): 
                    with gr.Row(): 
                        submit = gr.Button("Submit", variant="primary") 
                        stop = gr.Button("Stop") 
                        clear = gr.Button("Clear") 
        gr.Examples(examples, inputs=msg, label="Click on any example and press the 'Submit' button") 
        retriever_argument = gr.Accordion("Retriever Configuration", open=True) 
        with retriever_argument: with gr.Row(): 
            with gr.Row(): 
                do_rerank = gr.Checkbox( 
                    value=True, 
                    label="Rerank searching result", 
                    interactive=True, 
                ) 
            with gr.Row(): 
                vector_rerank_top_n = gr.Slider( 
                    1, 
                    10, 
                    value=2, 
                    step=1, 
                    label="Rerank top n", 
                    info="Number of rerank results", 
                    interactive=True, 
                ) 
            with gr.Row(): 
                vector_search_top_k = gr.Slider( 
                    1, 
                    50, 
                    value=10, 
                    step=1, 
                    label="Search top k", 
                    info="Search top k must >= Rerank top n", 
                    interactive=True, 
                ) 
    docs.clear(clear_files, outputs=[vector_store_status], queue=False) 
    load_docs.click( 
        create_vectordb, 
        inputs=[docs, spliter, chunk_size, chunk_overlap, vector_search_top_k, vector_rerank_top_n, do_rerank], 
        outputs=[vector_store_status], 
        queue=False, 
    ) 
    submit_event = msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then( 
        bot, 
        [chatbot, temperature, top_p, top_k, repetition_penalty, do_rag], 
        chatbot, 
        queue=True, 
    )
    submit_click_event = submit.click(user, [msg, chatbot], [msg, chatbot], queue=False).then( 
        bot, 
        [chatbot, temperature, top_p, top_k, repetition_penalty, do_rag], 
        chatbot, 
        queue=True, 
    ) 
    stop.click( 
        fn=request_cancel, 
        inputs=None, 
        outputs=None, 
        cancels=[submit_event, submit_click_event], 
        queue=False, 
    ) 
    clear.click(lambda: None, None, chatbot, queue=False) 
    vector_search_top_k.release( 
        update_retriever, 
        [vector_search_top_k, vector_rerank_top_n, do_rerank], 
    ) 
    vector_rerank_top_n.release( 
        update_retriever, 
        [vector_search_top_k, vector_rerank_top_n, do_rerank], 
    ) 
    do_rerank.change( 
        update_retriever, 
        [vector_search_top_k, vector_rerank_top_n, do_rerank], 
    ) 

demo.queue() 
# リモートで起動する場合は、server_name と server_port を指定 
# demo.launch(server_name='your server name', server_port='server port in int') 
# プラットフォーム上で起動する際に問題がある場合は、起動メソッドに share=True を渡すことができます: 
# demo.launch(share=True) 
# インターフェイスの公開共有可能なリンクを作成。詳細はドキュメントをご覧ください: https://gradio.app/docs/ 
demo.launch()
# gradio インターフェイスを停止するにはこのセルを実行 
demo.close()