人工知能 (AI) における推論スケーリングとは、大規模なトレーニング用データセットやモデルアーキテクチャに頼る代わりに、推論フェーズ(モデルが出力を生成する段階)中に計算リソースを割り当てることでモデルの性能を向上させる手法を指します。大規模言語モデル(LLM) はモデルパラメータとデータセットの両面で拡大を続けており、特にGPU上での推論時間の最適化と推論計算のスケーリングの管理が、高性能マルチモーダル 検索拡張生成(RAG) システムを導入する際の主な課題となっています。
計算リソースを増やし、テスト時に複雑なアルゴリズムを採用する最近の推論戦略の進歩により、LLMが複雑な推論タスクに取り組み、多様なインプット様式にわたってより高品質のアウトプットを提供する方法が再定義されています。推論スケーリングは、推論の深さを拡張することで思考の連鎖(CoT)を最適化します。この拡張により、モデルは反復プロンプトや複数のステップによる生成を通じて、より長く詳細な思考の連鎖を生成できるようになります。推論スケーリングは、モデルのサイズ、コンピューター予算、現実世界のアプリケーションに適した推論時間の実用的な最適化のバランスを重視した、マルチモーダルRAGの改善に活用できます。
その上、スケーリングの法則とベンチマークの結果からは、事前トレーニング、微調整、推論時間戦略、アウトプット選択のための高度なアルゴリズム間でのトレードオフが際立ちます。リソースに制約のあるシステムでも最先端のLLMのパフォーマンスに近づくことができるため、大規模なモデルも小規模なモデルも、推論スケーリングからメリットを得ることができます。このチュートリアルでは、最適化手法がモデルのパフォーマンスに与える影響を説明し、マルチモーダルRAGの導入にあたって精度、レイテンシー、コストのバランスをとるための実行可能なガイダンスを提供します。
このチュートリアルは、ドキュメント管理と高度な自然言語処理 (NLP)技術に関する知識を高めたいと考えている人工知能の開発者、研究者、愛好家向けに設計されています。推論スケーリングの力を活用して、過去のレシピで作成したマルチモーダルRAGパイプラインを改善する方法を学習します。チュートリアルでは、特にIBM Granite 大規模言語モデルに焦点を当てたマルチモーダルRAGの拡張性ストラテジーに焦点を当てていますが、同様の原則はOpenAI(GPT-4、GPT-4o、ChatGPTなど)やDeepMindのモデルを含むほとんどの一般的なモデルにも適用できます。
このチュートリアルでは、次のプロセスについて説明します。
このチュートリアルを完了すると、以下を達成できます。
従来の言語モデルは、いくつかの理由から長いコンテキストへの対応を苦手としています。
今回のチュートリアルで紹介する手法では、推論計算の戦略的な割り当てを通じて、これらの課題に対処します。
これら2つの高度な推論スケーリング技術(DRAGとIterDRAG)の詳細は、研究論文“Inference Scaling for Long-Context Retrieval Augmented Generation“で読むことができます。
これらの手法では、最適な割り当てをした場合に、推論計算のスケーリングがRAGの性能をほぼ直線的に向上させ、長いコンテクストを処理する最新のLLMの能力をより有効に活用できることが明らかになっています。この実装では、さまざまなモダリティーを処理できるIBM Graniteモデルを使用します。論文の原則を適用して、非構造化データからリアルタイムのユーザークエリに回答するAIシステムを作成します。
新たに作成した仮想環境でPython 3.10、3.11、または3.12を実行します。なお、このチュートリアルにはGitHubでもアクセスできます。
import sys
assert sys.version_info >= (3, 10) and sys.version_info < (3, 13), "Use Python 3.10, 3.11, or 3.12 to run this notebook."
! pip install "git+https://github.com/ibm-granite-community/utils.git" \
transformers \
pillow \
langchain_community \
langchain_huggingface \
langchain_milvus \
docling \
replicate
ロギング情報を確認するには、INFO log レベルを設定します。
注: このセルの実行は省略しても問題ありません。
import logging
logging.basicConfig(level=logging.INFO)
テキスト埋め込みベクトルの生成に使用する埋め込みモデルを指定します。ここでは、 Granite Embeddingsモデルの 1 つを使用します。
別の埋め込みモデルを使用するには、このコード・セルを埋め込みモデルレシピのコード・セルに置き換えます。
from langchain_huggingface import HuggingFaceEmbeddings
from transformers import AutoTokenizer
embeddings_model_path = "ibm-granite/granite-embedding-30m-english"
embeddings_model = HuggingFaceEmbeddings(
model_name=embeddings_model_path,
)
embeddings_tokenizer = AutoTokenizer.from_pretrained(embeddings_model_path)
画像理解に使用するMLLMを指定します。今回はGraniteビジョン・モデルを使用します。
from ibm_granite_community.notebook_utils import get_env_var
from langchain_community.llms import Replicate
from transformers import AutoProcessor
vision_model_path = "ibm-granite/granite-vision-3.2-2b"
vision_model = Replicate(
model=vision_model_path,
replicate_api_token=get_env_var("REPLICATE_API_TOKEN"),
model_kwargs={
"max_tokens": embeddings_tokenizer.max_len_single_sentence, # Set the maximum number of tokens to generate as output.
"min_tokens": 100, # Set the minimum number of tokens to generate as output.
"temperature": 0.01,
},
)
vision_processor = AutoProcessor.from_pretrained(vision_model_path)
RAG 生成操作に使用する言語モデルを指定します。今回はReplicate LangChainクライアントを使用して、Replicate上のibm- granite org から Graniteモデルに接続します。
Replicateをセットアップするには 「Replicateの使用をはじめる」を参照してください。
Replicate 以外のプロバイダーのモデルに接続するには、このコード・セルをLLM コンポーネントレシピのコード・セルに置き換えます。
model_path = "ibm-granite/granite-3.3-8b-instruct"
model = Replicate(
model=model_path,
replicate_api_token=get_env_var("REPLICATE_API_TOKEN"),
model_kwargs={
"max_tokens": 1000, # Set the maximum number of tokens to generate as output.
"min_tokens": 100, # Set the minimum number of tokens to generate as output.
"temperature": 0.01
},
)
tokenizer = AutoTokenizer.from_pretrained(model_path)
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions
pdf_pipeline_options = PdfPipelineOptions(
do_ocr=False,
generate_picture_images=True,
)
format_options = {
InputFormat.PDF: PdfFormatOption(pipeline_options=pdf_pipeline_options),
}
converter = DocumentConverter(format_options=format_options)
sources = [
"https://midwestfoodbank.org/images/AR_2020_WEB2.pdf",
]
conversions = { source: converter.convert(source=source).document for source in sources }
処理済みのドキュメントを使って、ドキュメント内のテキスト要素をさらに処理し、使用している埋め込みモデルに適したサイズに分割します。LangChainドキュメントのリストは、テキスト・チャンクから作成されます。
from docling_core.transforms.chunker.hybrid_chunker import HybridChunker
from docling_core.types.doc import DocItem, TableItem
from langchain_core.documents import Document
doc_id = 0
texts: list[Document] = []
for source, docling_document in conversions.items():
for chunk in HybridChunker(tokenizer=embeddings_tokenizer).chunk(docling_document):
items: list[DocItem] = chunk.meta.doc_items # type: ignore
if len(items) == 1 and isinstance(items[0], TableItem):
continue # we will process tables later
refs = " ".join(map(lambda item: item.get_ref().cref, items))
print(refs)
text = chunk.text
document = Document(
page_content=text,
metadata={
"doc_id": (doc_id:=doc_id+1),
"source": source,
"ref": refs,
},
)
texts.append(document)
print(f"{len(texts)} text document chunks created")
次に、ドキュメント内のテーブルを処理します。言語モデルが処理できるように、テーブルのデータをマークダウン形式に変換します。LangChainドキュメントのリストが、テーブルのマークダウン・レンダリングから作成されます。
from docling_core.types.doc import DocItemLabel
doc_id = len(texts)
tables: list[Document] = []
for source, docling_document in conversions.items():
for table in docling_document.tables:
if table.label in [DocItemLabel.TABLE]:
ref = table.get_ref().cref
print(ref)
text = table.export_to_markdown(docling_document)
document = Document(
page_content=text,
metadata={
"doc_id": (doc_id:=doc_id+1),
"source": source,
"ref": ref
},
)
tables.append(document)
print(f"{len(tables)} table documents created")
最後に文書内の画像を処理します。ここでは、画像の内容を理解するために視覚言語モデルを使用します。この例では、画像内のテキスト情報を知りたいとします。
画像プロンプトの選択は、モデルが画像のどの側面に焦点を当てるかを指示する重要なものです。例:
注:画像処理では、画像の数とビジョン言語モデルを実行するサービスによっては、かなりの処理時間が必要になる場合があります。
import base64
import io
import PIL.Image
import PIL.ImageOps
def encode_image(image: PIL.Image.Image, format: str = "png") -> str:
image = PIL.ImageOps.exif_transpose(image) or image
image = image.convert("RGB")
buffer = io.BytesIO()
image.save(buffer, format)
encoding = base64.b64encode(buffer.getvalue()).decode("utf-8")
uri = f"data:image/{format};base64,{encoding}"
return uri
# Feel free to experiment with this prompt
image_prompt = "Give a detailed description of what is depicted in the image"
conversation = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": image_prompt},
],
},
]
vision_prompt = vision_processor.apply_chat_template(
conversation=conversation,
add_generation_prompt=True,
)
pictures: list[Document] = []
doc_id = len(texts) + len(tables)
for source, docling_document in conversions.items():
for picture in docling_document.pictures:
ref = picture.get_ref().cref
print(ref)
image = picture.get_image(docling_document)
if image:
text = vision_model.invoke(vision_prompt, image=encode_image(image))
document = Document(
page_content=text,
metadata={
"doc_id": (doc_id:=doc_id+1),
"source": source,
"ref": ref,
},
)
pictures.append(document)
print(f"{len(pictures)} image descriptions created")
その後、入力したドキュメントから作成されたLangChainドキュメントを表示できます。
import itertools
from docling_core.types.doc import RefItem
from IPython.display import display
# Print all created documents
for document in itertools.chain(texts, tables):
print(f"Document ID: {document.metadata['doc_id']}")
print(f"Source: {document.metadata['source']}")
print(f"Content:\n{document.page_content}")
print("=" * 80) # Separator for clarity
for document in pictures:
print(f"Document ID: {document.metadata['doc_id']}")
source = document.metadata['source']
print(f"Source: {source}")
print(f"Content:\n{document.page_content}")
docling_document = conversions[source]
ref = document.metadata['ref']
picture = RefItem(cref=ref).resolve(docling_document)
image = picture.get_image(docling_document)
print("Image:")
display(image)
print("=" * 80) # Separator for clarity
埋め込みモデルを使用して、テキスト・チャンクからドキュメントと生成された画像キャプションをベクトル・データベースにロードします。このベクトル・データベースを作成すると、ドキュメント全体で意味的な類似性検索を簡単に実行できます。
注:ベクトル・データベースの移植には、埋め込みモデルとサービスによっては、かなりの処理時間が必要になる場合があります。
埋め込みベクトルの保存と取得に使用するデータベースを指定します。このチュートリアルでは、Langchain 経由でMilvus を使用します。Milvusはベクトル・データベースとして、ニューラル・ネットワークやさまざまなMLアルゴリズムの作成した数値埋め込みの保管、インデックス付け、管理を行います。
Milvus 以外のベクトル・データベースに接続するには、このコード・セルをベクトル・ストアレシピのコード・セルに置き換えます。
import tempfile
from langchain_core.vectorstores import VectorStore, VectorStoreRetriever
from langchain_milvus import Milvus
db_file = tempfile.NamedTemporaryFile(prefix="vectorstore_", suffix=".db", delete=False).name
print(f"The vector database will be saved to {db_file}")
vector_db: VectorStore = Milvus(
embedding_function=embeddings_model,
connection_args={"uri": db_file},
auto_id=True,
enable_dynamic_field=True,
index_params={"index_type": "AUTOINDEX"},
)
ここで、テキスト、テーブル、画像の説明用のすべてのLangChainドキュメントをベクトル・データベースに追加します。
import itertools
documents = list(itertools.chain(texts, tables, pictures))
ids = vector_db.add_documents(documents)
print(f"{len(ids)} documents added to the vector database")
retriever: VectorStoreRetriever = vector_db.as_retriever(search_kwargs={"k": 10})
ドキュメントの変換とベクトル化が正常に完了したので、RAGパイプラインを設定できます。
ここでは、ベクトル空間内のクエリに関連する情報を含むチャンクを検索することにより、ベクトル・データベースをテストします。検索された画像の説明に関連する文書を表示します。
この検証ステップは、完全なRAGパイプラインを構築する前に、検索システムが正しく動作していることを確認する重要なものです。返されたドキュメントがクエリに関連しているかどうかを確認します。
さまざまなクエリを自由に試してください。
query = "Analyze how Midwest Food Bank's financial efficiency changed during the pandemic by comparing their 2019 and 2020 performance metrics. What specific pandemic adaptations had the greatest impact on their operational capacity, and how did their volunteer management strategy evolve to maintain service levels despite COVID-19 restrictions? Provide specific statistics from the report to support your analysis."
for doc in vector_db.as_retriever().invoke(query):
print(doc)
print("=" * 80) # Separator for clarity
返されるドキュメントは、クエリの応答になっている必要があります。ここからRAGパイプラインを構築します。
返されるドキュメントは、クエリの応答になっている必要があります。ここからRAGパイプラインを構築します。
初めに、GraniteがRAGクエリーを実行するためのプロンプトを作成します。Graniteチャット・テンプレートを使用し、LangChain RAGパイプラインが置き換えるプレースホルダー値を提供します。
{context}は、前回の検索で示されたように取得されたチャンクを保持し、質問に答えるためのドキュメント・コンテキストとしてモデルに渡します。
次に、作成したGraniteプロンプト・テンプレートを使用してRAGパイプラインを構築します。
from ibm_granite_community.notebook_utils import escape_f_string
from langchain.prompts import PromptTemplate
from langchain.chains.retrieval import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
# Create a Granite prompt for question-answering with the retrieved context
prompt = tokenizer.apply_chat_template(
conversation=[{
"role": "user",
"content": "{input}",
}],
documents=[{
"doc_id": "0",
"text": "{context}",
}],
add_generation_prompt=True,
tokenize=False,
)
prompt_template = PromptTemplate.from_template(template=escape_f_string(prompt, "input", "context"))
# Create a Granite document prompt template to wrap each retrieved document
document_prompt_template = PromptTemplate.from_template(template="""\
<|end_of_text|>
<|start_of_role|>document {{"document_id": "{doc_id}"}}<|end_of_role|>
{page_content}""")
document_separator=""
# Assemble the retrieval-augmented generation chain
combine_docs_chain = create_stuff_documents_chain(
llm=model,
prompt=prompt_template,
document_prompt=document_prompt_template,
document_separator=document_separator,
)
rag_chain = create_retrieval_chain(
retriever=retriever,
combine_docs_chain=combine_docs_chain,
)
パイプラインはクエリを使用してベクトル・データベースからドキュメントを検索し、それらをクエリのコンテキストとして使用します。
outputs = rag_chain.invoke({"input": query})
print(outputs['answer'])
標準的なRAGアプローチも比較的適切に機能しますが、長いコンテンツや複雑なコンテンツを扱う場合は、いくつかの重要な限界があります。
推論スケーリングの手法では、推論時により多くのコンピューティングリソースを戦略的に割り当てることで、これらの制限に対処します。
ここで、研究論文「Inference Scaling for Long-Context Retrieval Augmented Generation」の DRAG テクニックを実装して、RAG システムを強化します。
DRAGは、コンテキスト内の例を使用して、文書から情報を抽出して使用する方法をモデルに示し、コンテキストが長い場合の性能を向上させます。
通常は質の高い質問と応答のペアからなる、キュレーション済みのデータセットから取得できます。この目的のために、予想されるドメインに一致する合成例をいくつか作成します。
ここでは、個々のデモンストレーションを表すデータクラスを定義し、複数のデモンストレーションを作成します。
from dataclasses import dataclass, field, InitVar
from langchain_core.documents import Document
@dataclass
class DRAG_Demonstration:
query: str
answer: str
retriever: InitVar[VectorStoreRetriever] = field(kw_only=True)
documents: list[Document] = field(default_factory=list, kw_only=True)
def __post_init__(self, retriever: VectorStoreRetriever):
if not self.documents:
self.documents = retriever.invoke(self.query)
def __format__(self, format_spec: str) -> str:
formatted_documents = "\n".join(
f"Document {i+1}:\n{document.page_content}"
for i, document in enumerate(self.documents)
)
return f"""\
{formatted_documents}
Question: {self.query}
Answer: {self.answer}
"""
def create_enhanced_drag_demonstrations(vector_db: VectorStore) -> list[DRAG_Demonstration]:
"""Create high-quality demonstrations for DRAG technique that showcase effective document analysis"""
demonstration_retriever: VectorStoreRetriever = vector_db.as_retriever(search_kwargs={"k": 5})
demonstrations = [
DRAG_Demonstration(
query="How did the COVID-19 pandemic impact Midwest Food Bank's operations in 2020?",
answer="The COVID-19 pandemic significantly impacted Midwest Food Bank's operations in 2020. Despite challenges, MFB remained open and responsive to increased needs. They implemented safety protocols, reduced volunteer numbers for social distancing, and altered their distribution model to allow partner agencies to receive food safely. The pandemic created unprecedented food insecurity, with many people seeking assistance for the first time. MFB distributed 37% more food than in 2019, with a record 179 semi-loads of Disaster Relief family food boxes sent nationwide. The organization also faced supply chain disruptions and food procurement challenges in the early months but continued to find and distribute food. Community, business, and donor support helped fund operations and food purchases. Additionally, MFB began participating in the USDA Farmers to Families Food Box program in May 2020, distributing over $52 million worth of nutritious produce, protein, and dairy products.",
retriever=demonstration_retriever
),
DRAG_Demonstration(
query="What role did volunteers play at Midwest Food Bank during 2020, and how were they affected by the pandemic?",
answer="Volunteers were described as 'the life-blood of the organization' in the 2020 annual report. Despite the pandemic creating safety challenges, volunteers demonstrated courage and dedication by increasing their hours to meet growing needs. MFB implemented safety protocols at each location and limited volunteer group sizes to allow for social distancing. This created a challenge as food needs increased while fewer volunteers were available to help. To address this gap, multiple MFB locations received assistance from the National Guard, who filled vital volunteer positions driving trucks, operating forklifts, and helping with food distributions. In 2020, 17,930 individuals volunteered 300,898 hours of service, equivalent to 150 full-time employees. The volunteer-to-staff ratio was remarkable with 450 volunteers for every 1 paid MFB staff member, highlighting the volunteer-driven nature of the organization during the crisis.",
retriever=demonstration_retriever
),
DRAG_Demonstration(
query="How did Midwest Food Bank's international programs perform during 2020, particularly in Haiti and East Africa?",
answer="In 2020, Midwest Food Bank's international operations in East Africa and Haiti faced unique challenges but continued to serve communities. In East Africa (operated as Kapu Africa), strict lockdowns led to mass hunger, especially in slum areas. Kapu Africa distributed 7.2 million Tender Mercies meals, working with partner ministries to share food in food-insecure slums. A notable outcome was a spiritual awakening among recipients, with many asking why they were receiving help. In Haiti, the pandemic added to existing challenges, closing airports, seaports, factories, and schools. MFB Haiti more than doubled its food shipments to Haiti, delivering over 160 tons of food relief, nearly three-quarters being Tender Mercies meals. As Haitian children primarily receive nourishment from school lunches, MFB Haiti distributed Tender Mercies through faith-based schools and also partnered with over 20 feeding centers serving approximately 1,100 children daily. Nearly 1 million Tender Mercies meals were distributed in Haiti during 2020.",
retriever=demonstration_retriever
),
]
return demonstrations
次に、すべてのデモをプロンプト用にフォーマットします。
# Format all demonstrations together
demonstrations = create_enhanced_drag_demonstrations(vector_db)
formatted_demonstrations = "\n\n".join(
f"Example {i+1}:\n{demo}"
for i, demo in enumerate(demonstrations)
)
次に、フォーマット済みのデモンストレーション例を含むモデルのDRAGプロンプトを作成します。
drag_prompt = tokenizer.apply_chat_template(
conversation=[{
"role": "user",
"content": f"""\
Here are examples of effectively extracting information from documents to answer questions.
{formatted_demonstrations}
Follow these examples when answering the user's question:
{{input}}""",
}],
documents=[{
"doc_id": "0",
"text": "Placeholder{context}",
}],
add_generation_prompt=True,
tokenize=False,
)
# Convert to prompt template
drag_prompt_template = PromptTemplate.from_template(template=escape_f_string(drag_prompt, "input", "context"))
通常、レトリーバーは類似性の順序でドキュメントを返します。最も類似性の高いドキュメントが最初に返されます。結果の順序を逆にするために、並べ替え用レトリーバーを定義します。これにより最も類似したドキュメントが末尾、つまりプロンプトの終わり近くに表示されるようになります。
import typing
from langchain_core.retrievers import BaseRetriever, RetrieverInput, RetrieverOutput
from langchain_core.callbacks.manager import CallbackManagerForRetrieverRun
class ReorderingRetriever(BaseRetriever):
base_retriever: BaseRetriever
def _get_relevant_documents(
self, query: RetrieverInput, *, run_manager: CallbackManagerForRetrieverRun, **kwargs: typing.Any
) -> RetrieverOutput:
docs = self.base_retriever._get_relevant_documents(query, run_manager=run_manager, **kwargs)
return list(reversed(docs)) # Reverse the order so higher-ranked docs are closer to query in prompt
reordering_retriever = ReorderingRetriever(base_retriever=retriever)
DRAGプロンプト・テンプレートと並べ替え用レトリーバーを使用して、DRAGクエリのパイプラインを作成します。
drag_combine_docs_chain = create_stuff_documents_chain(
llm=model,
prompt=drag_prompt_template,
document_prompt=document_prompt_template,
document_separator=document_separator,
)
drag_chain = create_retrieval_chain(
retriever=reordering_retriever,
combine_docs_chain=drag_combine_docs_chain,
)
drag_outputs = drag_chain.invoke({"input": query})
print("\n=== DRAG-Enhanced Answer ===")
print(drag_outputs['answer'])
例を与えたことで、回答に改善が見られます。次はさらに徹底したRAGテクニックを試してみましょう。
IterDRAGは、複雑なクエリーをより単純なサブクエリーに分解し、反復的な検索を実行することでDRAGを拡張します。このアプローチは、複数のソースからの情報を統合したり、いくつかのステップにわたって推論する必要がある複雑なマルチホップ問題で特に効果を発揮します。
反復的アプローチの主なメリット:
分解ステップは、複雑なクエリーを取得し、それを個別に回答可能な、より単純で焦点を絞ったサブクエリーに分割する重要なものです。
decompose_prompt = tokenizer.apply_chat_template(
conversation=[{
"role": "user",
"content": """\
You are a helpful assistant that breaks down complex questions into simpler sub-questions.
For multi-part or complex questions, generate 1-3 sub-questions that would help answer the main question.
Here are examples of how to decompose complex questions:
{demonstrations}
Follow the above examples when breaking down the user's question.
If the following question is already simple enough, just respond with "No follow-up needed."
Otherwise, break down the following question into simpler sub-questions. Format your response as:
Follow up: [sub-question]
Question: {input}"""
}],
add_generation_prompt=True,
tokenize=False,
)
decompose_prompt_template = PromptTemplate.from_template(template=escape_f_string(decompose_prompt, "input", "demonstrations"))
decompose_chain = decompose_prompt_template | model
下位クエリへの応答コンポーネントは、関連するドキュメントを取得し、焦点を絞った中間回答を生成して、個々の下位質問を処理します。
intermediate_prompt = tokenizer.apply_chat_template(
conversation=[{
"role": "user",
"content": """\
You are a helpful assistant that answers specific questions based on the provided documents.
Focus only on the sub-question and provide a concise intermediate answer.
Please answer the following sub-question based on the provided documents.
Format your response as:
Intermediate answer: [your concise answer to the sub-question]
Sub-question: {input}
"""
}],
documents=[{
"doc_id": "0",
"text": "Placeholder{context}",
}],
add_generation_prompt=True,
tokenize=False,
)
intermediate_prompt_template = PromptTemplate.from_template(template=escape_f_string(intermediate_prompt, "input", "context"))
intermediate_combine_docs_chain = create_stuff_documents_chain(
llm=model,
prompt=intermediate_prompt_template,
document_prompt=document_prompt_template,
document_separator=document_separator,
)
intermediate_chain = create_retrieval_chain(
retriever=reordering_retriever,
combine_docs_chain=intermediate_combine_docs_chain,
)
最終的な回答生成コンポーネントでは、すべての中間回答を組み合わせて、元の質問に対する包括的な回答を生成します。
final_prompt = tokenizer.apply_chat_template(
conversation=[{
"role": "user",
"content": """\
You are a helpful assistant that provides comprehensive answers to questions.
Use the intermediate answers to sub-questions to formulate a complete final answer.
Please provide a final answer to the main question based on the intermediate answers to sub-questions.
Format your response as:
So the final answer is: [your comprehensive answer to the main question]
Main question: {input}
Sub-questions and intermediate answers:
{context}"""
}],
add_generation_prompt=True,
tokenize=False,
)
final_prompt_template = PromptTemplate.from_template(template=escape_f_string(final_prompt, "input", "context"))
final_chain = final_prompt_template | model
IterDRAGの性能にとっては、効果的なデモンストレーションの作成が非常に重要です。これらの例は、モデルに次の方法を示します。
@dataclass
class IterDRAG_Demonstration_Base:
query: str
answer: str
@dataclass
class IterDRAG_Demonstration(IterDRAG_Demonstration_Base):
intermediate: list[IterDRAG_Demonstration_Base]
def __format__(self, format_spec: str) -> str:
sub_questions="\n".join(
f"Follow up: {sub.query}"
for sub in self.intermediate
)
return f"Question: {self.query}\n{sub_questions}"
def create_iterdrag_demonstrations() -> list[IterDRAG_Demonstration]:
"""Create examples showing how to decompose and answer complex questions"""
demonstrations = [
IterDRAG_Demonstration(
query="What impact did the pandemic have on the food bank's operations and distribution?",
answer="The pandemic had a profound impact on food bank operations and distribution. Distribution volume increased by 60% to over 100 million pounds of food in 2020. Operationally, the food bank faced supply chain disruptions, volunteer shortages, and safety protocol challenges. In response, they implemented contactless distribution, expanded mobile pantries, created emergency food boxes for vulnerable populations, and developed virtual nutrition education. Despite these challenges, they successfully scaled operations to meet the unprecedented community need during the crisis.",
intermediate=[
IterDRAG_Demonstration_Base(
query="How did food distribution volume change during the pandemic?",
answer="Food distribution volume increased by 60% during the pandemic, rising from approximately 62 million pounds in 2019 to over 100 million pounds in 2020.",
),
IterDRAG_Demonstration_Base(
query="What operational challenges did the food bank face during the pandemic?",
answer="The food bank faced challenges including supply chain disruptions, volunteer shortages due to social distancing requirements, and the need to implement new safety protocols for food handling and distribution.",
),
IterDRAG_Demonstration_Base(
query="What new programs were implemented in response to the pandemic?",
answer="New programs included contactless distribution methods, expanded mobile pantry operations, emergency food boxes for vulnerable populations, and virtual nutrition education classes.",
),
],
),
IterDRAG_Demonstration(
query="How does the food bank's financial management compare to industry standards for non-profits?",
answer="The food bank demonstrates excellent financial management compared to industry standards. With 94% of its budget allocated to program services and only 6% to administrative and fundraising costs, it exceeds the industry benchmark of 85-90% for program spending. This financial efficiency places the food bank among the top-performing non-profits in terms of maximizing donor impact and minimizing overhead expenses.",
intermediate=[
IterDRAG_Demonstration_Base(
query="What percentage of the food bank's budget goes to program services versus administrative costs?",
answer="94% of the food bank's budget goes directly to program services, with only 6% allocated to administrative and fundraising costs.",
),
IterDRAG_Demonstration_Base(
query="What are the industry standards for program spending versus overhead for food banks?",
answer="Industry standards suggest that well-run food banks typically allocate 85-90% of their budget to program services, with 10-15% for administrative and fundraising expenses.",
),
],
),
]
return demonstrations
この関数は反復プロセス全体の調整を行うものです。
import re
def iterative_drag(main_question: str) -> dict[str, typing.Any]:
"""
Implements IterDRAG: decomposing queries, retrieving documents for sub-queries,
and generating a final answer based on intermediate answers.
"""
print(f"\n=== Processing query with IterDRAG: '{main_question}' ===")
# Step 1: Decompose the main question into sub-questions
print("Step 1: Decomposing the query into sub-questions...")
iterdrag_demonstrations = create_iterdrag_demonstrations()
formatted_demonstrations = "\n\n".join(
f"Example {i+1}:\n{demo}"
for i, demo in enumerate(iterdrag_demonstrations)
)
decompose_result = decompose_chain.invoke({
"input": main_question,
"demonstrations": formatted_demonstrations,
})
decompose_answer = decompose_result
# Extract sub-questions using regex
sub_questions = re.findall(r"Follow up: (.*?)(?=Follow up:|\n|$)", decompose_answer, re.DOTALL)
sub_questions = [sq.strip() for sq in sub_questions if sq.strip()]
if not sub_questions:
print("No decomposition needed or found. Using standard DRAG approach.")
return drag_chain.invoke({"input": main_question})
print(f"Decomposed into {len(sub_questions)} sub-questions")
# Step 2: Answer each sub-question
intermediate_pairs: list[dict[str, str]] = []
for i, sub_question in enumerate(sub_questions):
print(f"\nStep 2.{i+1}: Processing sub-question: '{sub_question}'")
# Generate answer for this sub-question
intermediate_result = intermediate_chain.invoke({"input": sub_question})
intermediate_answer = intermediate_result["answer"]
# Extract intermediate answer using regex
intermediate_answer_match = re.search(r"Intermediate answer: (.*?)$", intermediate_answer, re.DOTALL)
if intermediate_answer_match:
intermediate_answer = intermediate_answer_match.group(1).strip()
print(f"Generated intermediate answer: {intermediate_answer[:100]}...")
# Store the sub-question and its answer
intermediate_pairs.append({"input": sub_question, "answer": intermediate_answer})
# Step 3: Generate the final answer based on sub-question answers
print("\nStep 3: Generating final answer based on intermediate answers...")
final_result = final_chain.invoke({
"input": main_question,
"context": "\n\n".join(
f"Sub-question: {pair['input']}\nIntermediate answer: {pair['answer']}"
for pair in intermediate_pairs
),
})
final_answer = final_result
# Extract final answer
final_answer_match = re.search(r"So the final answer is: (.*?)$", final_answer, re.DOTALL)
if final_answer_match:
final_answer = final_answer_match.group(1).strip()
return {"input": main_question, "answer": final_answer, "intermediate": intermediate_pairs}
3つのRAGアプローチをすべてセットアップしたので、違いを確認するために、より複雑にした同一のクエリに対する回答を比較してみましょう。
比較は、それぞれのアプローチのメリットと、それぞれの使用に最適なケースを理解するのに役立ちます。
# Run all approaches on the same complex query
comparison_query = "What was the full impact chain of the National Guard's assistance during the pandemic? Specifically, how did their involvement affect volunteer operations, what specific tasks did they perform, and how did this ultimately translate to community impact in terms of food distribution capabilities and reach?"
print("\n=== Standard RAG ===")
standard_result = rag_chain.invoke({"input": comparison_query})
print(standard_result["answer"])
print("\n=== DRAG ===")
drag_result = drag_chain.invoke({"input": comparison_query})
print(drag_result["answer"])
print("\n=== IterDRAG ===")
iterdrag_result = iterative_drag(comparison_query)
print(iterdrag_result["answer"])
ここでは、実装された3つのRAGアプローチの性能の違いを要約します。
アプローチ
| 長所
| 制限
| 最適なユースケース
|
|---|---|---|---|
標準RAG |
|
|
|
DRAG |
|
|
|
IterDRAG |
|
|
|
これまで見てきたとおり、DRAGやIterDRAGなどの実装推論スケーリング手法はRAGの性能を大幅に向上させることができます。この方法は、複数のドキュメントの詳細な分析を必要とする複雑なクエリに特に適しています。
このチュートリアルでは、推論スケーリングによってRAGの性能がどのように劇的に向上するかを説明します。DRAGやIterDRAGなどの手法を用いて推論時に追加の計算を戦略的に割り当てることで、複雑なクエリに対する応答品質を大幅に向上させることができます。
高価な推論: 自己注意メカニズムを使用するTransformerベースのモデルには、インプットに応じて二次関数的に拡張する推論コストがかかります。この方法では、長いコンテキストの処理にコストがかかり、RAGの実用的なアプリケーションが短いドキュメントに限られたり、積極的な切り捨てが必要になる場合があります。
コンテキスト活用の限界:標準のRAGシステムは、多くの場合固定数のドキュメントを取得して処理するため、複雑なマルチホップのクエリの場合は不十分になる場合があります。取得した多くのパッセージにわたって情報を合成するには不向きであるため、コンテキスト長が長い場合、特に128,000トークンを超えるとパフォーマンスが限界に達します。
非効率的な計算割り当て: 慎重な割り当てを行わずに、読み込むドキュメントやコンテキストを増やすと、精度が比例することなく単に計算コストが増加し、情報過多となるため、応答の減少や性能の低下につながります。
デモンストレーションベースのRAG(DRAG):
DRAGは、複数の取得例、質問と答えをプロンプト内でデモンストレーションとして活用し、モデルがコンテキストに沿って関連情報を見つけて適用する方法を学習できるようにします。
このアプローチは、モデルが注意メカニズムを圧倒することなく豊富なコンテキストを利用できるため、有効なコンテキスト長が短い場合に特に効果的であり、検索と生成の品質の両方が向上します。
反復的デモンストレーションベースのRAG(IterDRAG)
IterDRAGは、複雑なクエリーをより単純なサブクエリーに分解し、各サブステップの回答を繰り返し取得して生成します。
取得と生成をインターリーブすることで、IterDRAGはマルチホップ・クエリーのギャップを埋める推論チェーンを構築するため、非常に長いコンテキストの場合に特に効果的です。
このプロセスにより、モデルは計算をより効率的に割り当てることができ、各ステップで最も関連性の高い情報に焦点を当て、長いコンテキストにおける注意の過負荷のリスクを回避できます。これらの推論スケーリング手法をRAGアプリケーションに適用することで、基盤となるモデルを変更することなく、知識集約型のタスクでパフォーマンスを大幅に向上させることができます。
AI開発者向けの次世代エンタープライズ・スタジオであるIBM watsonx.aiを使用して、生成AI、基盤モデル、機械学習機能をトレーニング、検証、チューニング、導入しましょう。わずかなデータとわずかな時間でAIアプリケーションを構築できます。
業界をリードするIBMのAI専門知識とソリューション製品群を使用すれば、ビジネスにAIを活用できます。
AIの導入で重要なワークフローと業務を再構築し、エクスペリエンス、リアルタイムの意思決定とビジネス価値を最大化します。
1. “A Survey of Frontiers in LLM Reasoning: Inference Scaling, Learning to Reason, and Agentic Systems,” Ke, Zixuan, Fangkai Jiao, Yifei Ming, Xuan-Phi Nguyen, Austin Xu, Do Xuan Long, Minzhi Li, et al., ArXiv.org, 2025.
2. “Reasoning in Granite 3.2 Using Inference Scaling,” Lastras, Luis. 2025, IBM Research, IBM, February 26, 2025.
3. “Inference Scaling for Long-Context Retrieval Augmented Generation,” Zhenrui Yue, Honglei Zhuang, Aijun Bai, Kai Hui, Rolf Jagerman, Hansi Zeng, Zhen Qin, Dong Wang, Xuanhui Wang, Michael Bendersky, ArXiv.org, 2024.