Il ridimensionamento dell'inferenza nell'intelligenza artificiale (AI) si riferisce a tecniche che migliorano le prestazioni del modello allocando risorse computazionali durante la fase di inferenza (quando i modelli generano output) piuttosto che fare affidamento su set di dati di addestramento più grandi o architetture di modelli. Poiché i modelli linguistici di grandi dimensioni (LLM) continuano a espandersi sia nei parametri del modello che nella scala del set di dati, l'ottimizzazione del tempo di inferenza e la gestione della scalabilità del calcolo dell'inferenza, in particolare sull'hardware GPU, sono diventate sfide centrali per implementare sistemi ad alte prestazioni di retrieval-augmented generation (RAG).
I recenti progressi nelle strategie di inferenza che aumentano le risorse e utilizzano algoritmi complessi in fase di test stanno ridefinendo il modo in cui gli LLM affrontano compiti di ragionamento complessi e forniscono output di qualità superiore in diverse modalità di input. Il ridimensionamento dell'inferenza ottimizza la chain of thought (CoT) ampliando la profondità del ragionamento. Questa espansione consente ai modelli di produrre chain of thought più lunghe e dettagliate attraverso il prompt iterativo o la generazione in più fasi. La scalabilità dell'inferenza può essere utilizzata per migliorare la RAG multimodale, perché si concentra sull'interazione tra dimensioni del modello, budget dei computer e l'ottimizzazione pratica dei tempi di inferenza per applicazioni del mondo reale.
Inoltre, le leggi sulla scalabilità e i risultati dei benchmark enfatizzano i compromessi tra strategie di preformazione, messa a punto, tempo di inferenza e algoritmi avanzati per la selezione degli output. Sia i modelli più grandi che quelli più piccoli traggono beneficio dalla scalabilità dell'inferenza poiché consente anche ai sistemi a risorse limitate di avvicinarsi alle prestazioni degli LLM all'avanguardia. Questo tutorial dimostra l'impatto delle tecniche di ottimizzazione sulle prestazioni del modello, offrendo una guida attuabile per bilanciare precisione, latenza e costi nelle distribuzioni RAG multimodali.
Questo tutorial è progettato per sviluppatori, ricercatori e appassionati di intelligenza artificiale che desiderano migliorare la propria conoscenza della gestione dei documenti e delle tecniche avanzate di elaborazione del linguaggio naturale (NLP). Imparerai come sfruttare la potenza della scalabilità dell'inferenza per migliorare la pipeline RAG multimodale creata nella procedura precedente. Sebbene questo tutorial si concentri sulle strategie per la scalabilità nel RAG multimodale focalizzato specificamente sui modelli IBM® Granite Large Language, principi simili sono applicabili ai modelli più diffusi, inclusi quelli di OpenAI (ad esempio, GPT-4, GPT-4o, ChatGPT) e DeepMind.
Questo tutorial illustra i processi seguenti:
In questo tutorial, utilizzerai anche tre tecnologie all'avanguardia:
Al termine di questo tutorial sarai in grado di:
I modelli linguistici tradizionali hanno difficoltà con contesti lunghi per diversi motivi:
Le tecniche di questo tutorial risolvono queste sfide attraverso l'allocazione strategica del calcolo dell'inferenza.
Ulteriori informazioni su queste due tecniche avanzate di scaling dell'inferenza (DRAG e IterDrag) sono disponibili nel documento di ricerca "Inference Scaling for Long-Context Retrieval Augmented Generation"
Questi metodi dimostrano che la scalabilità del calcolo dell'inferenza può migliorare le prestazioni RAG in modo quasi lineare se allocate in modo ottimale, consentendo ai sistemi RAG di utilizzare meglio le funzionalità di lungo contesto degli LLM moderni. Per questa implementazione, utilizzeremo un modello IBM® Granite in grado di elaborare diverse modalità. Creerai un sistema AI per rispondere alle domande degli utenti in tempo reale provenienti da dati non strutturati, applicando i principi del documento.
Assicurati di eseguire Python 3.10, 3.11 o 3.12 in un ambiente virtuale appena creato. Nota: puoi accedere a questo tutorial anche su 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
Per visualizzare alcune informazioni di registrazione, possiamo configurare il livello di registro INFO.
NOTA: se vuoi, puoi saltare l'esecuzione di questa cella.
import logging
logging.basicConfig(level=logging.INFO)
Specifica il modello di embedding da utilizzare per generare vettori di embedding del testo. Qui utilizzeremo uno dei modelli Granite Embeddings.
Per utilizzare un modello di embedding diverso, sostituisca questa cella di codice con una tratta da questa procedura Embeddings Model.
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)
Specifica il file MLLM da utilizzare per la comprensione dell'immagine. Useremo il modello Granite Vision.
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)
Specifica il modello linguistico da utilizzare per l'operazione di generazione RAG. Qui utilizzeremo il client Replicate LangChain per connetterci a un modello Granite dell'organizzazione ibm-granite su Replicate.
Per configurare Replicate, vedi Guida introduttiva a Replicate.
Per connettersi a un modello su un provider diverso da Replicate, sostituire questa cella di codice con una della procedura del componente 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 }
Una volta elaborati i documenti, elaboriamo ulteriormente gli elementi di testo nei documenti e li suddividiamo in dimensioni appropriate per il modello di embedding che stiamo utilizzando. Dai blocchi di testo viene creato un elenco di documenti 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")
A seguire, elaboriamo tutte le tabelle presenti nei documenti. Convertiamo i dati della tabella in formato markdown in modo che il modello linguistico possa elaborarli. Un elenco di documenti LangChain viene creato dai rendering markdown della tabella.
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")
Infine, elaboriamo tutte le immagini nei documenti. Qui usiamo il modello linguistico di visione per comprendere il contenuto delle immagini. In questo esempio, siamo interessati a qualsiasi informazione testuale nell'immagine.
La scelta di un prompt appropriato è critica in quanto indica su quali aspetti dell'immagine si concentrerà il modello. Ad esempio:
NOTA: l'elaborazione delle immagini potrebbe richiedere un tempo di elaborazione molto lungo, a seconda del numero di immagini e del servizio eseguito dal modello linguistico di visione.
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")
Possiamo quindi visualizzare i documenti LangChain creati dai documenti di input.
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
Utilizzando l'embedding, carichiamo i documenti dai blocchi di testo e dalle didascalie delle immagini generate in un database vettoriale. La creazione di questo database vettoriale ci consente di condurre facilmente una ricerca di somiglianza semantica tra i nostri documenti.
NOTA: La popolazione del database vettoriale potrebbe richiedere un tempo di elaborazione molto lungo, a seconda del modello di embedding e del servizio.
Specifica il database da utilizzare per memorizzare e recuperare i vettori di embedding. Per lo scopo di questo tutorial utilizzeremo Milvus tramite Langchain. Come database vettoriale, Milvus memorizzerà, indicizzerà e gestirà gli embedding numerici generati da reti neurali e vari algoritmi di ML.
Per connetterti a un database vettoriale diverso da Milvus, sostituisci questa cella di codice con una di questa procedura di database vettoriale.
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"},
)
Ora, aggiungiamo tutti i documenti LangChain per il testo, le tabelle e le descrizioni delle immagini al database vettoriale.
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})
Ora che abbiamo convertito e vettorializzato con successo i nostri documenti, possiamo impostare la nostra pipeline RAG.
Qui testiamo il database vettoriale cercando blocchi con informazioni pertinenti alla nostra query nello spazio vettoriale. Mostriamo i documenti associati alla descrizione dell'immagine recuperata.
Questa fase di convalida è importante per garantire che il nostro sistema di recupero funzioni correttamente prima di costruire la pipeline RAG completa. Vogliamo vedere se i documenti restituiti sono rilevanti per la nostra query.
Puoi provare query diverse.
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
I documenti restituiti devono rispondere alla query. Andiamo avanti e costruiamo la nostra pipeline RAG.
I documenti restituiti devono rispondere alla query. Andiamo avanti e costruiamo la nostra pipeline RAG.
Per prima cosa, creiamo i prompt affinché Granite esegua la query RAG. Utilizziamo il modello di chat Granite e forniamo i valori segnaposto che la pipeline LangChain RAG sostituirà.
{context} conterrà i blocchi recuperati, come mostrato nella ricerca precedente, e li invierà al modello come contesto del documento per rispondere alla nostra domanda.
Quindi, costruiamo la pipeline RAG utilizzando i modelli di prompt di Granite che abbiamo creato.
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,
)
La pipeline utilizza la query per individuare i documenti dal database vettoriale e utilizzarli come contesto per la query.
outputs = rag_chain.invoke({"input": query})
print(outputs['answer'])
Sebbene l'approccio RAG standard funzioni ragionevolmente bene, presenta diverse limitazioni chiave quando si tratta di contenuti lunghi o complessi:
Le tecniche di scalabilità dell'inferenza risolvono queste limitazioni allocando strategicamente più calcolo al momento dell'inferenza.
Ora implementeremo la tecnica DRAG tratta dal documento di ricerca "Inference Scaling for Long-Context Retrieval Augmented Generation" per migliorare il nostro sistema RAG.
DRAG utilizza esempi contestuali per dimostrare al modello come estrarre e utilizzare informazioni dai documenti, migliorando le prestazioni per scenari a lungo termine.
In genere, questi dati provengono da un set di dati curato di coppie QA di alta qualità. A questo scopo, creeremo alcuni esempi sintetici che corrispondono al dominio previsto.
Qui definiamo una classe di dati per rappresentare una dimostrazione individuale e quindi creiamo alcune dimostrazioni.
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
Quindi formattiamo tutte le dimostrazioni insieme per il prompt.
# 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)
)
A seguire, creiamo il prompt DRAG per il modello che include gli esempi dimostrativi formattati.
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"))
Normalmente, il retriever restituirà i documenti in ordine di somiglianza, dove il documento più simile è il primo. Definiamo un retriever di riordino per invertire l'ordine dei risultati. L'ordine ora mostra il documento più simile per ultimo, quindi più vicino alla fine del prompt.
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)
Creiamo la pipeline per la query DRAG utilizzando il modello di prompt DRAG e il reordering retriever.
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'])
Ottimo, abbiamo ottenuto alcuni miglioramenti nella risposta fornendo alcuni esempi. Ora proviamo una tecnica RAG ancora più approfondita!
IterDrag estende DRAG scomponendo le query complesse in sottoquery più semplici ed eseguendo il recupero interlacciato. Questo approccio è particolarmente efficace per domande complesse con più passaggi che richiedono l'integrazione di informazioni provenienti da più fonti o il ragionamento in più fasi.
Vantaggi fondamentali dell'approccio iterativo:
La fase di scomposizione è critica perché richiede una query complessa e la suddivide in query secondarie più semplici e mirate a cui è possibile rispondere individualmente.
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
Il componente di risposta alle query secondarie gestisce ogni singola sottodomanda recuperando i documenti pertinenti e generando risposte intermedie mirate.
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,
)
La componente finale per la generazione delle risposte combina tutte le risposte intermedie per produrre una risposta completa alla domanda originale.
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
Creare dimostrazioni efficaci è fondamentale per le prestazioni di IterDrag. Questi esempi mostrano al modello come:
@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
Questa funzione orchestra l'intero processo iterativo:
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}
Ora che abbiamo impostato tutti e tre gli approcci RAG, confrontiamo le loro risposte alla stessa domanda, questa volta molto più complessa, per vedere le differenze.
Il confronto ci aiuterà a comprendere i vantaggi di ciascun approccio e quando potrebbe essere più appropriato utilizzarlo.
# 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"])
Qui riassumiamo le differenze tra le prestazioni dei tre approcci RAG implementati:
Approach
| Punti di forza
| Limitazioni
| Migliori casi d'uso
|
|---|---|---|---|
RAG standard |
|
|
|
DRAG |
|
|
|
IterDRAG |
|
|
|
Come abbiamo visto nella nostra implementazione, le tecniche di ridimensionamento dell'inferenza, come DRAG e IterDRAG, possono migliorare significativamente le prestazioni RAG. Questo metodo è particolarmente vero per le query complesse che richiedono un'analisi approfondita di più documenti.
In questo tutorial, abbiamo esplorato come il ridimensionamento dell'inferenza può migliorare notevolmente le prestazioni RAG. Allocando strategicamente calcoli aggiuntivi al momento dell'inferenza attraverso tecniche come DRAG e IterDRAG, possiamo ottenere miglioramenti sostanziali nella qualità delle risposte per query complesse.
Inferenza costosa: i modelli basati sui trasformatori, che utilizzano meccanismi di auto-attenzione, hanno costi di inferenza che scalano quadraticamente con la lunghezza dell'input. Questo metodo rende la gestione di contesti lunghi dispendiosa dal punto di vista computazionale, limitando l'applicazione pratica di RAG a documenti più brevi o richiedendo un troncamento aggressivo.
Utilizzo del contesto limitato: i sistemi RAG standard spesso recuperano ed elaborano un numero fisso di documenti che possono essere insufficienti per query complesse e multihop. Le prestazioni si stabilizzano con l'aumentare della lunghezza del contesto, soprattutto oltre i 128.000 token, perché il modello fatica a sintetizzare le informazioni in molti passaggi recuperati.
Allocazione computazionale inefficiente: senza un'allocazione accurata, l'aggiunta di altri documenti o contesti recuperati aumenta semplicemente i costi computazionali senza aumenti proporzionali in termini di precisione, con conseguente diminuzione dei rendimenti o addirittura un peggioramento delle prestazioni a causa del sovraccarico di informazioni.
RAG basata su dimostrazioni (DRAG):
La DRAG utilizza diversi esempi recuperati, domande e risposte come dimostrazioni all'interno del prompt, consentendo al modello di imparare in contesto come individuare e applicare le informazioni pertinenti.
Questo approccio è particolarmente efficace per contesti più brevi in quanto consente al modello di utilizzare un contesto ricco senza sovraccaricare il meccanismo di attenzione, migliorando sia il recupero che la qualità della generazione.
RAG iterativa basata sulle dimostrazioni (IterDRAG):
IterDRAG scompone query complesse in query secondarie più semplici, recuperando e generando risposte iterative per ogni passaggio secondario.
Inserendo recupero e generazione, IterDrag crea catene di ragionamento che colmano il divario per le query multihop, rendendolo particolarmente efficace per contesti eccezionalmente lunghi.
Questo processo consente al modello di allocare il calcolo in modo più efficiente, concentrandosi sulle informazioni più rilevanti in ogni fase ed evitando il rischio di un sovraccarico di attenzione a lungo termine. Applicando queste tecniche di scalabilità dell'inferenza alle sue applicazioni RAG, può ottenere prestazioni molto migliori in attività ad alta intensità di conoscenze senza modificare i modelli sottostanti.
Addestra, convalida, adatta e implementa le funzionalità di AI generativa, foundation model e machine learning con IBM watsonx.ai, uno studio aziendale di nuova generazione per builder AI. Crea applicazioni AI in tempi ridotti e con una minima quantità di dati.
Metti l'AI al servizio della tua azienda grazie all'esperienza leader di settore e alla gamma di soluzioni di IBM nel campo dell'AI.
Reinventa i flussi di lavoro e le operazioni critiche aggiungendo l'AI per massimizzare le esperienze, il processo decisionale in tempo reale e il valore di business.
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, 26 febbraio 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.