El escalado de inferencias en inteligencia artificial (IA) se refiere a técnicas que mejoran el rendimiento del modelo asignando recursos computacionales durante la fase de inferencia (cuando los modelos generan outputs) en lugar de depender de conjuntos de datos de entrenamiento más grandes o arquitecturas de modelos. A medida que los modelos de lenguaje de gran tamaño (LLM) continúan expandiéndose tanto en parámetros de modelo como en escala de conjunto de datos, optimizar el tiempo de inferencia y gestionar el escalado de cómputo de inferencia, particularmente en hardware de GPU, se han convertido en desafíos centrales para implementar sistemas de generación aumentada por recuperación (RAG) de alto rendimiento.
Los recientes avances en las estrategias de inferencia que aumentan los recursos y emplean algoritmos complejos en el momento de la prueba están redefiniendo la forma en que los LLM abordan tareas de razonamiento complejas y ofrecen output de mayor calidad en diversas modalidades de entrada. El escalado de inferencias optimiza la cadena de pensamiento (CoT) ampliando la profundidad del razonamiento. Esta expansión permite a los modelos producir cadenas de pensamiento más largas y detalladas a través de prompts iterativos o generación de varios pasos. El escalado de inferencias se puede aprovechar para mejorar el RAG multimodal, centrándose en la interacción entre los tamaños de los modelos, los presupuestos informáticos y la optimización práctica del tiempo de inferencia para aplicaciones del mundo real.
Además, las leyes de escalado y los resultados de referencia enfatizan las compensaciones entre el preentrenamiento, el ajuste, las estrategias de tiempo de inferencia y los algoritmos avanzados para la selección de output. Tanto los modelos más grandes como los más pequeños se benefician del escalado de inferencia, ya que también permite que los sistemas con recursos limitados se acerquen al beneficio de los LLM de vanguardia. Este tutorial demuestra el impacto de las técnicas de optimización en el rendimiento del modelo, ofreciendo una guía que se puede ejecutar para equilibrar la precisión, la latencia y el coste en las implementaciones de RAG multimodales.
Este tutorial está diseñado para desarrolladores, investigadores y entusiastas de la inteligencia artificial que buscan mejorar sus conocimientos sobre gestión de documentos y técnicas avanzadas de procesamiento del lenguaje natural (PLN). Aprenderá a aprovechar el poder del escalado de inferencias para mejorar el pipeline RAG multimodal creado en una receta anterior. Aunque este tutorial se centra en las estrategias de escalabilidad en RAG multimodal centradas específicamente en los grandes modelos de lenguaje de IBM Granite, principios similares son aplicables a los modelos más populares, incluidos los de OpenAI (por ejemplo, GPT-4, GPT-4o, ChatGPT) y DeepMind .
Este tutorial le guiará a través de los siguientes procesos:
Durante este tutorial, también utilizará tres tecnologías punta:
Al final de este tutorial, habrá logrado lo siguiente:
Los modelos lingüísticos tradicionales tienen dificultades con los contextos largos por varias razones:
Las técnicas en este tutorial abordan estos desafíos mediante la asignación estratégica del cálculo de inferencias.
Puede encontrar más información sobre estas dos técnicas avanzadas de escalado de inferencia (DRAG e IterDRAG) en el documento de investigación “Inference Scaling for Long-Context Retrieval Augmented Generation“
Estos métodos muestran que escalar el cálculo de inferencia puede mejorar el rendimiento de RAG casi linealmente cuando se asigna de manera óptima, lo que permite a los sistemas RAG hacer un mejor uso de las capacidades de contexto largo de los LLM modernos. Para esta implementación, utilizaremos un modelo IBM Granite capaz de procesar diferentes modalidades. Creará un sistema de IA para responder a las consultas de los usuarios en tiempo real a partir de datos no estructurados, aplicando los principios del documento.
Asegúrese de ejecutar Python 3.10, 3.11 o 3.12 en un entorno virtual recién creado. Tenga en cuenta que también puede acceder a este tutorial en 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
Para ver información de registro, podemos configurar el nivel de registro INFO.
NOTA: Puede omitir la ejecución de esta celda.
import logging
logging.basicConfig(level=logging.INFO)
Especifique el modelo de incrustaciones que se utilizará para generar vectores de embedding de texto. Aquí utilizaremos uno de los modelos de Granite Embeddings.
Para utilizar un modelo de embedding diferente, sustituya esta celda de código por una de esta receta del modelo de embeddings.
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)
Especifique el MLLM que se utilizará para la comprensión de imágenes. Utilizaremos el modelo de 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)
Especifique el modelo de lenguaje que se utilizará para la operación de generación de RAG. Aquí utilizamos el cliente Replicate LangChain para conectarnos a un modelo Granite de la organización ibm-granite en Replicate.
Para configurar Replicate, consulte Primeros pasos con Replicate.
Para conectarse a un modelo en un proveedor que no sea Replicate, sustituya esta celda de código por una de la receta 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 vez procesados los documentos, procesamos aún más los elementos de texto de los documentos y los fragmentamos en tamaños adecuados para el modelo de embeddings que estamos utilizando. Se crea una lista de documentos LangChain a partir de los fragmentos de texto.
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 continuación, procesamos las tablas de los documentos. Convertimos los datos de la tabla al formato Markdown para que el modelo de lenguaje pueda procesarlos. Se crea una lista de documentos LangChain a partir de las representaciones de Markdown de la tabla.
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")
Por último, procesamos las imágenes de los documentos. Aquí utilizamos el modelo de lenguaje de visión para comprender el contenido de las imágenes. En este ejemplo, nos interesa cualquier información textual de la imagen.
Elegir una instrucción de imagen crítica es fundamental, ya que indica en qué aspectos de la imagen se centrará el modelo. Por ejemplo:
NOTA: El procesamiento de imágenes puede requerir un tiempo de procesamiento significativo en función del número de imágenes y del servicio que ejecuta el modelo de lenguaje de visión.
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")
A continuación, podemos mostrar los documentos LangChain creados a partir de los documentos de entrada.
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
Utilizando el embedding, cargamos los documentos de los fragmentos de texto y los subtítulos de imágenes generados en una base de datos vectorial. La creación de esta base de datos vectorial nos permite realizar fácilmente una búsqueda de similitud semántica en nuestros documentos.
NOTA: El llenado de la base de datos vectorial puede requerir un tiempo de procesamiento significativo en función del modelo y el servicio de embedding.
Especifique la base de datos que se utilizará para almacenar y recuperar vectores de embedding. A los efectos de este tutorial, utilizaremos Milvus via Langchain. Como base de datos vectorial, Milvus almacenará, indexará y gestionará embeddings numéricos generados por redes neuronales y varios algoritmos de ML (machine learning).
Para conectarse a una base de datos vectorial distinta de Milvus, reemplace esta celda de código con una de esta receta de almacén de vectores.
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"},
)
Ahora, añadimos todos los documentos LangChain para el texto, las tablas y las descripciones de imágenes a la base de datos vectorial.
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})
Ahora que hemos convertido correctamente nuestros documentos y los hemos vectorizado, podemos configurar nuestro pipeline RAG.
Aquí probamos la base de datos vectorial buscando fragmentos con información relevante para nuestra consulta en el espacio vectorial. Mostramos los documentos asociados a la descripción de la imagen recuperada.
Este paso de validación es importante para ayudar a garantizar que nuestro sistema de recuperación funcione correctamente antes de construir nuestro pipeline completo de RAG. Queremos ver si los documentos devueltos son relevantes para nuestra consulta.
No dude en probar diferentes consultas.
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
Los documentos devueltos deben responder a la consulta. Avancemos y construyamos nuestro pipeline RAG.
Los documentos devueltos deben responder a la consulta. Avancemos y construyamos nuestro pipeline RAG.
En primer lugar, creamos las instrucciones para que Granite realice la consulta RAG. Utilizamos la plantilla de chat Granite y proporcionamos los valores de marcador de posición que reemplazará el pipeline LangChain RAG.
{context} contendrá los fragmentos recuperados, como se muestra en la búsqueda anterior, y alimentará el modelo como contexto del documento para responder a nuestra pregunta.
A continuación, construimos el pipeline RAG utilizando las plantillas de instrucciones de Granite que hemos creado.
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,
)
El pipeline utiliza la consulta para localizar documentos de la base de datos vectorial y utilizarlos como contexto para la consulta.
outputs = rag_chain.invoke({"input": query})
print(outputs['answer'])
Aunque el enfoque RAG estándar funciona razonablemente bien, tiene varias limitaciones clave cuando se trata de contenido largo o complejo:
Las técnicas de escalado de inferencia dirigen estas limitaciones asignando estratégicamente más computación en el momento de la inferencia.
Ahora implementaremos la técnica DRAG del documento de investigación "Inference Scaling for Long-Context Retrieval Augmented Generation" para mejorar nuestro sistema RAG.
DRAG utiliza ejemplos en contexto para demostrar al modelo cómo extraer y utilizar información de documentos, mejorando el rendimiento para escenarios de contexto largo.
Normalmente procederían de un conjunto de datos seleccionados de pares de control de calidad de alta calidad. Para ello, crearemos algunos ejemplos sintéticos que coincidan con el dominio esperado.
Aquí, definimos una clase de datos para representar una demostración individual y luego creamos algunas demostraciones.
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
A continuación, formateamos todas las demostraciones juntas para la instrucción.
# 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)
)
Después, creamos la instrucción DRAG para el modelo, que incluye los ejemplos de demostración formateados.
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, el recuperador devolverá los documentos en orden de similitud, siendo el primero el documento más similar. Definimos un recuperador de reordenación para invertir el orden de los resultados. El pedido ahora muestra el documento más similar en último lugar, por lo tanto, más cerca del final de la instrucción.
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)
Creamos la canalización para la consulta DRAG utilizando la plantilla de instrucción DRAG y el recuperador de reordenación.
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'])
Genial, parece que hemos mejorado la respuesta dándole algunos ejemplos. ¡Probemos a continuación una técnica RAG aún más completa!
IterDRAG amplía DRAG descomponiendo consultas complejas en subconsultas más sencillas y realizando una recuperación intercalada. Este enfoque es particularmente eficaz para preguntas complejas de varios saltos que requieren integrar información de múltiples fuentes o razonar en varios pasos.
Beneficios clave del enfoque iterativo:
El paso de descomposición es crítico porque toma una consulta compleja y la divide en subconsultas más simples y específicas que pueden responderse 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
El componente de respuesta a subconsultas gestiona cada subpregunta individual recuperando documentos relevantes y generando respuestas intermedias específicas.
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,
)
El componente de generación de respuestas final combina todas las respuestas intermedias para producir una respuesta completa a la pregunta original.
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
La creación de demostraciones eficaces es crucial para el rendimiento de IterDRAG. Estos ejemplos muestran al modelo cómo:
@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
Esta función orquesta todo el proceso 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}
Ahora que tenemos configurados los tres enfoques RAG, comparemos sus respuestas a la misma consulta, esta vez mucho más compleja para ver las diferencias.
La comparación nos ayudará a comprender los beneficios de cada enfoque y cuándo podría ser más apropiado utilizar cada uno.
# 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"])
A continuación resumimos las diferencias de rendimiento entre los tres enfoques RAG implementados:
Método
| Puntos fuertes
| Limitaciones
| Mejores casos de uso
|
|---|---|---|---|
RAG estándar |
|
|
|
DRAG |
|
|
|
IterDRAG |
|
|
|
Como hemos visto en nuestra implementación, las técnicas de escalado de inferencia, como DRAG e IterDRAG, pueden mejorar significativamente el rendimiento de RAG. Este método es especialmente cierto para consultas complejas que requieren un análisis profundo de varios documentos.
En este tutorial, hemos explorado cómo el escalado de inferencias puede mejorar drásticamente el rendimiento de RAG. Mediante la asignación estratégica de potencia de computación adicional en el momento de la inferencia, mediante técnicas como DRAG e IterDRAG, podemos lograr mejoras sustanciales en la calidad de la respuesta a consultas complejas.
Inferencia costosa: los modelos basados en transformadores, que utilizan mecanismos de autoatención, tienen costes de inferencia que se escalan cuadráticamente con la longitud de entrada. Este método hace que el manejo de contextos largos sea computacionalmente costoso, limitando la aplicación práctica de RAG a documentos más cortos o requiriendo un truncamiento agresivo.
Utilización limitada del contexto: los sistemas RAG estándar a menudo recuperan y procesan un número fijo de documentos que pueden ser insuficientes para consultas complejas de varios saltos. El rendimiento se estanca a medida que aumenta la longitud del contexto, especialmente más allá de 128 000 tokens, porque el modelo tiene dificultades para sintetizar la información en muchos pasajes recuperados.
Asignación de computación ineficiente: sin una asignación cuidadosa, agregar más documentos o contexto recuperados simplemente aumenta el coste computacional sin ganancias proporcionales en precisión, lo que conduce a rendimientos decrecientes o incluso a una degradación del rendimiento debido a la sobrecarga de información.
RAG basado en demostraciones (DRAG):
DRAG aprovecha múltiples ejemplos recuperados, preguntas y respuestas como demostraciones dentro de la instrucción, lo que permite al modelo aprender en contexto cómo localizar y aplicar información relevante.
Este enfoque es especialmente eficaz para longitudes de contexto efectivas más cortas, ya que permite que el modelo utilice un contexto enriquecido sin sobrecargar el mecanismo de atención, lo que mejora tanto la recuperación como la calidad de la generación.
RAG iterativo basado en demostraciones (IterDRAG):
IterDRAG descompone las consultas complejas en subconsultas más sencillas, recuperando y generando respuestas de forma iterativa para cada subpaso.
Al intercalar la recuperación y la generación, IterDRAG crea cadenas de razonamiento que cierran la brecha para las consultas multisalto, lo que lo hace especialmente eficaz para contextos excepcionalmente largos.
Este proceso permite al modelo asignar el cálculo de manera más eficiente, centrándose en la información más relevante en cada paso y evitando el riesgo de sobrecarga de atención de contexto largo. Al aplicar estas técnicas de escalado de inferencia a sus aplicaciones RAG, puede lograr un rendimiento significativamente mejor en tareas intensivas en conocimiento sin cambiar sus modelos subyacentes.
Entrene, valide, ajuste e implemente IA generativa, modelos fundacionales y capacidades de machine learning con IBM watsonx.ai, un estudio empresarial de nueva generación para desarrolladores de IA. Cree aplicaciones de IA en menos tiempo y con menos datos.
Ponga la IA a trabajar en su negocio con la experiencia líder en IA del sector de IBM y junto a su cartera de soluciones.
Reinvente las operaciones y flujos de trabajo críticos añadiendo IA para maximizar las experiencias, la toma de decisiones en tiempo real y el valor empresarial.
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 de febrero de 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.