Escalonamento de inferência para melhorar a RAG multimodal

Escalonamento de inferência para melhorar a RAG multimodal 

O escalonamento de inferência em inteligência artificial (IA) refere-se a técnicas que aprimoram o desempenho do modelo ao alocar recursos computacionais durante a fase de inferência (quando os modelos geram saídas) em vez de depender de conjuntos de dados de treinamento maiores ou arquiteturas de modelos. Conforme os grandes modelos de linguagem (LLMs) continuam a se expandir em termos de parâmetros do modelo e escala do conjunto de dados, a otimização do tempo de inferência e o gerenciamento do dimensionamento de computação de inferência (particularmente no hardware da GPU) tornaram-se desafios centrais para implementar sistemas de geração aumentada de recuperação (RAG) multimodal de alto desempenho. 

Introdução ao escalonamento de inferência

Avanços recentes em estratégias de inferência que aumentam Recursos computacionais e empregam algoritmos complexos no momento do teste estão redefinindo a forma como os LLMs lidam com tarefas de raciocínio complexas e oferecem saídas de maior qualidade em diversas modalidades de entrada. O dimensionamento de inferência otimiza a cadeia de pensamento (CoT) expandindo a profundidade do raciocínio. Essa expansão permite que os modelos produzam cadeias de pensamento mais longas e detalhadas por meio de prompts iterativos ou geração de várias etapas. A escala de inferência pode ser aproveitada para melhorar a RAG multimodal, com foco na interação entre tamanhos de modelos, orçamentos de computadores e a otimização prática do tempo de inferência para aplicações do mundo real.

Além disso, as leis de escala e os resultados de benchmarkd enfatizam as trocas entre pré-treinamento, ajuste fino, estratégias de tempo de inferência e algoritmos avançados para seleção de saída. Tanto os modelos maiores quanto os menores se beneficiam do dimensionamento de inferência, pois ele também permite que sistemas com recursos limitados se aproximem do desempenho de LLMs de ponta. Este tutorial demonstra o impacto das técnicas de otimização no desempenho do modelo, oferecendo orientações praticáveis para equilibrar precisão, latência e custo em implementações da RAG multimodal.

Este tutorial foi criado para desenvolvedores, pesquisadores e entusiastas de inteligência artificial que desejam aprimorar seus conhecimentos sobre gerenciamento de documentos e técnicas avançadas de processamento de linguagem natural (NLP). Você aprenderá a aproveitar o poder do dimensionamento de inferência para aprimorar o pipeline da RAG multimodal criado em uma receita anterior. Embora este tutorial se concentre em estratégias para escalabilidade na RAG multimodal focada especificamente em grandes modelos de linguagem do IBM® Granite, princípios semelhantes são aplicáveis aos modelos mais populares, incluindo os da OpenAI (por exemplo, GPT-4, GPT-4o, ChatGPT) e da DeepMind .

Este tutorial guiará você pelos seguintes processos:

  • Pré-processamento de documentos: você aprenderá a lidar com documentos de várias fontes, analisá-los e transformá-los em formatos utilizáveis e armazená-los em bancos de dados de vetores usando o Docling. O Docling é um toolkit de código aberto da IBM usado para analisar formatos de documentos com eficiência, como PDF, DOCX, PPTX, XLSX, imagens, HTML, AsciiDoc e Markdown. Em seguida, exporta o conteúdo do documento em formatos legíveis por máquina, como Markdown ou JSON. Você utilizará um modelo de aprendizado de máquina (ML) Granite para gerar descrições de imagens nos documentos. Neste tutorial, o Docling baixará os documentos em PDF e os processará para que possamos obter o texto e as imagens contidos nos documentos. Neste tutorial, o Docling baixará os documentos em PDF e os processará para que possamos obter o texto e as imagens contidos nos documentos.

  • Geração aumentada de recuperação (RAG): entenda como conectar LLMs como o Granite com bases de conhecimento externas para melhorar respostas de consulta e gerar insights valiosos. A RAG é uma técnica de grandes modelos de linguagem (LLMs) usada para conectar LLMs a uma base de conhecimento de informações fora dos dados nos quais o LLM foi treinado. Essa técnica é aplicada a LLMs sem a necessidade de ajustes. A RAG tradicional é limitado a casos de uso, como sumarização de texto e chatbots.

  • RAG multimodal: saiba como a RAG multimodal usa grandes modelos de linguagem multimodais (MLLMs) para processar informações de vários tipos de dados. Esses dados podem, então, ser incluídos como parte da base de conhecimento externa usada na RAG. Dados multimodais podem incluir texto, imagens, áudio, vídeo ou outras formas. Neste tutorial, usamos o mais recente modelo de visão multimodal da IBM, o Granite Vision 3.2.

  • Implementação da RAG baseada em demonstração (DRAG) e RAG baseada em demonstração iterativa (IterDRAG): aplique as técnicas de dimensionamento de inferência do artigo de pesquisa para melhorar significativamente o desempenho da RAG ao trabalhar com contexto longo. O método da DRAG aproveita o aprendizado em contexto para melhorar o desempenho da RAG. Ao incluir vários exemplos de RAGs como demonstrações, a DRAG ajuda os modelos a aprender a localizar informações relevantes em contextos longos. Ao contrário da RAG padrão, que pode estagnar com mais documentos, a DRAG mostra melhorias lineares com maior comprimento de contexto. A IterDRAG é uma extensão da DRAG que lida com consultas complexas de vários saltos, decompondo-as em subconsultas mais simples. O Multihop é um processo em que uma consulta complexa é dividida e respondida em subperguntas simples. Cada subpergunta pode exigir informações recuperadas e/ou sintetizadas de diferentes fontes. A IterDRAG intercala etapas de recuperação e geração, criando cadeias de raciocínio que preenchem lacunas de composição. Essa abordagem é particularmente eficaz para lidar com consultas complexas em contextos longos.

  • LangChain para integração de fluxos de trabalho: descubra como usar o LangChain para simplificar e orquestrar fluxos de trabalho de processamento e recuperação de documentos, permitindo interação sem dificuldades entre diferentes componentes do sistema.

Durante este tutorial, você também usará três tecnologias de ponta:

  1. Docling: um toolkit de código aberto usado para analisar e converter documentos.

  2. Granite: uma família de LLMs de última geração que fornece recursos robustos de linguagem natural e um modelo de linguagem de visão que reproduz a geração de texto para texto.

  3. LangChain: um framework poderoso usado para criar aplicações baseadas em modelos de linguagem, projetados para simplificar fluxos de trabalho complexos e integrar ferramentas externas sem dificuldades.

Até o final deste tutorial, você realizará o seguinte:

  • Obter proficiência em pré-processamento de documentos, fragmentação e compreensão de imagens.

  • Integrar bancos de dados de vetores para aprimorar os recursos de recuperação.

  • Implementar a DRAG e a IterDRAG para realizar recuperação de dados eficiente e precisa com escala de inferência.

  • Experimentar em primeira mão como o escalonamento da computação de inferência pode levar a melhorias de desempenho quase lineares no desempenho da RAG.

Compreensão dos desafios de contexto longo

Os modelos de linguagem tradicionais têm dificuldade com contextos longos por vários motivos:

  • Mecanismos de atenção tradicionais, como transformadores, escalam quadraticamente, o que pode demandar imensos recursos computacionais. 

  • Dificuldade em localizar informações relevantes em sequências muito longas. 

  • Desafios na preservação da coerência em partes distantes da entrada. 

  • Aumento das demandas computacionais para o processamento de sequências longas.

As técnicas neste tutorial lidam com esses desafios por meio da alocação estratégica da computação de inferência.

Métodos de escalonamento de inferência: DRAG e IterDRAG

DRAG versus IterDRAG
DRAG versus IterDRAG

Mais informações sobre essas duas técnicas avançadas de escalonamento de inferência (DRAG e IterDRAG) podem ser encontradas no artigo de pesquisa “Inference Scaling for Long-Context Retrieval Augmented Generation

Esses métodos mostram que o escalonamento da computação de inferência pode melhorar o desempenho da RAG quase linearmente quando alocado de forma ideal, permitindo que os sistemas de RAG façam melhor uso dos recursos de contexto longo dos LLMs modernos. Para essa implementação, usaremos um modelo IBM Granite capaz de processar diferentes modalidades. Você criará um sistema de IA para responder a consultas de usuários em tempo real com dados não estruturados, aplicando os princípios do artigo.

Pré-requisitos

  • Familiaridade com programação Python.

  • Conhecimento básico de LLMs, conceitos de NLP e computer vision.

Etapas

Certifique-se de estar executando o Python 3.10, 3.11 ou 3.12 em um ambiente virtual recém-criado. Observe que você também pode acessar este tutorial no GitHub.

Etapa 1: Configuração do ambiente

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."

Etapa 2: Instalação de dependências

! pip install "git+https://github.com/ibm-granite-community/utils.git" \
    transformers \
    pillow \
    langchain_community \
    langchain_huggingface \
    langchain_milvus \
    docling \
    replicate

Registro

Para ver algumas informações de registro, podemos configurar o nível de log INFO.

OBSERVAÇÃO: não há problema em ignorar a execução dessa célula.

import logging

logging.basicConfig(level=logging.INFO)

Etapa 3: Seleção dos modelos de IA

Carregue os modelos Granite

Especifique o modelo de embeddings a ser usado para gerar vetores de embeddings de texto. Aqui, usaremos um dos modelos de embeddings do Granite.

Para usar um modelo de embeddings diferente, substitua essa célula de código por uma dessa receita do 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 o MLLM a ser usado para compreensão da imagem. Usaremos o modelo de visão 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)

Especifique o modelo de linguagem a ser usado para a operação de geração de RAG. Aqui, usamos o cliente Replicate LangChain para nos conectar a um modelo Granite a partir da organização ibm-granite no Replicate.

Para configurar o Replicate, consulte Introdução ao Replicate.

Para conectar a um modelo em um provedor que não seja o Replicate, substitua esta célula de código por uma da receita do componente do LLMa.

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)

Etapa 4: Preparação dos documentos para o banco de dados de vetores com o Docling

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 }

Com os documentos processados, processamos ainda mais os elementos de texto nos documentos e os fragmentamos em tamanhos apropriados para o modelo de embeddings que estamos usando. Uma lista de documentos do LangChain é criada a partir dos 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")

Em seguida, processamos todas as tabelas nos documentos. Convertemos os dados da tabela em formato de remarcação para que o modelo de linguagem possa processá-los. Uma lista de documentos do LangChain é criada a partir das renderizações de remarcação da tabela.

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 fim, processamos todas as imagens dos documentos. Aqui, usamos o modelo de linguagem da visão para entender o conteúdo das imagens. Neste exemplo, estamos interessados em qualquer informação textual na imagem.

A escolha de um prompt apropriado é crítica, pois direciona em quais aspectos da imagem o modelo se concentrará. Por exemplo:

  • Um prompt como "Forneça uma descrição detalhada do que está representado na imagem" (usado abaixo) fornecerá informações gerais sobre todos os elementos visuais.

  • Um prompt como "Qual texto aparece nesta imagem?" se concentraria especificamente na extração de conteúdo textual.

  • Um prompt como "Descreva a visualização de dados gráfica nesta imagem" seria melhor para gráficos e tabelas.

  • Você deve experimentar diferentes prompts com base nos tipos de imagens em seus documentos e nas informações que você precisa extrair delas.

OBSERVAÇÃO: o processamento de imagens pode exigir um tempo de processamento significativo com base no número de imagens e no serviço que executa o modelo de linguagem de visão.

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")

Podemos, então, exibir os documentos do LangChain criados a partir dos 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

Preencha o banco de dados de vetores

Usando o modelo de embedding, carregamos os documentos dos fragmentos de texto e legendas de imagens geradas para um banco de dados de vetores. A criação desse banco de dados de vetores nos permite realizar facilmente uma pesquisa de similaridade semântica entre nossos documentos.

OBSERVAÇÃO: a população do banco de dados de vetores pode exigir um tempo de processamento significativo, dependendo do seu modelo de embedding e serviço.

Escolha seu banco de dados de vetores

Especifique o banco de dados a ser utilizado para armazenar e recuperar vetores de embedding. Para os fins deste tutorial, usaremos o Milvus via Langchain. Como um banco de dados de vetores, o Milvus irá armazenar, indexar e gerenciar embeddings gerados por redes neurais e vários algoritmos de ML.

Para conectar a um banco de dados de vetores diferente do Milvus, substitua esta célula de código por uma dessa receita do armazenamento de vetores.

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"},
)

Agora, adicionamos todos os documentos do LangChain para o texto, tabelas e descrições de imagem ao banco de dados de vetores.

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})

Etapa 5: RAG com o Granite

Agora que convertemos nossos documentos e os vetorizamos com sucesso, podemos configurar nosso pipeline da RAG.

Valide a qualidade da recuperação

Aqui testamos o banco de dados de vetores procurando fragmentos com informações relevantes para nossa consulta no espaço vetorial. Exibimos os documentos associados à descrição da imagem recuperada.

Essa etapa de validação é importante para ajudar a garantir que nosso sistema de recuperação esteja funcionando corretamente antes de criarmos nosso pipeline da RAG completo. Queremos ver se os documentos devolvidos são relevantes para nossa consulta.

Fique à vontade para testar 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

Os documentos devolvidos devem responder à consulta. Vamos em frente construir nosso pipeline da RAG.

Os documentos devolvidos devem responder à consulta. Vamos em frente construir nosso pipeline da RAG.

Crie o pipeline da RAG para o Granite

Primeiro, criamos os prompts para que o Granite realize a consulta de RAG. Usamos o modelo de chat Granite e fornecemos os valores do espaço reservado que o pipeline da RAG LangChain substituirá.

{context} manterá os fragmentos recuperados, conforme mostrado na pesquisa anterior, e as alimentará no modelo como contexto de documento para responder à nossa pergunta.

Em seguida, construímos o pipeline da RAG usando os modelos de prompts do Granite que criamos.

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,
)

Gere uma resposta com recuperação aprimorada a uma pergunta

O pipeline usa a consulta para localizar documentos do banco de dados de vetores e usá-los como contexto para a consulta.

outputs = rag_chain.invoke({"input": query})
print(outputs['answer'])

Limitações da RAG padrão e por que precisamos do escalonamento de inferência

Embora a abordagem padrão da RAG funcione razoavelmente bem, ela tem várias limitações importantes ao lidar com conteúdo longo ou complexo:

  1. Gerenciamento de contexto: ao lidar com muitos documentos, a RAG padrão tem dificuldades para utilizar de forma eficaz todo o contexto disponível.

  2. Qualidade da recuperação: sem orientação sobre como usar as informações recuperadas, os modelos muitas vezes se concentram nas partes erradas dos documentos.

  3. Raciocínio composto: o processo de compreensão de consultas complexas que requerem raciocínio em várias etapas é um desafio para a RAG padrão.

  4. O desempenho fica estagnado: adicionar mais documentos à RAG padrão geralmente resulta em resultados decrescentes após um determinado limite.

As técnicas de escalonamento de inferência lidam com essas limitações alocando estrategicamente mais computação no tempo de inferência.

RAG aprimorada com DRAG (RAG baseada em demonstração)

Agora, implementaremos a técnica de DRAG do artigo de pesquisa "Inference Scaling for Long-Context Retrieval Augmented Generation" para aprimorar nosso sistema de RAG.

A DRAG usa exemplos em contexto para demonstrar ao modelo como extrair e usar informações de documentos, melhorando o desempenho para cenários de contexto longo.

Etapa 1: Crie exemplos de demonstrações em contexto

Elas normalmente viriam de um conjunto de dados selecionado de pares de QA de alta qualidade. Para isso, criaremos alguns exemplos sintéticos que correspondam ao domínio esperado.

Aqui, definimos uma classe de dados para representar uma demonstração individual e, em seguida, criamos algumas demonstrações.

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

Etapa 2: Formate as demonstrações para inclusão no prompt

Em seguida, formatamos todas as demonstrações juntas para o 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)
)

Etapa 3: Crie o modelo de prompts de DRAG

Em seguida, criamos o prompt de DRAG para o modelo, que inclui os exemplos de demonstração formatados.

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"))

Etapa 4: Crie um recuperador personalizado que reordene documentos

Normalmente, o recuperador retornará os documentos em ordem de similaridade, com o documento mais semelhante primeiro. Definimos um recuperador de reordenação para reverter a ordem dos resultados. A ordem agora exibe o documento mais semelhante por último, portanto, mais próximo do final do 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)

Etapa 5: Crie o pipeline da DRAG

Criamos o pipeline para a consulta da DRAG usando o modelo de prompts de DRAG e o recuperador de reordenamento.

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,
)

Etapa 6: Gere uma resposta aprimorada por DRAG a uma pergunta

drag_outputs = drag_chain.invoke({"input": query})
print("\n=== DRAG-Enhanced Answer ===")
print(drag_outputs['answer'])

Ótimo, parece que conseguimos algumas melhorias na resposta fornecendo alguns exemplos. Vamos tentar uma técnica de RAG ainda mais completa a seguir!

Implementação da IterDRAG (RAG baseado em demonstração iterativa)

A IterDRAG estende a DRAG decompondo consultas complexas em subconsultas mais simples e realizando recuperação intercalada. Essa abordagem é particularmente eficaz para questões complexas com vários saltos, que exigem a integração de informações de várias fontes ou raciocínio em várias etapas.
 
Principais benefícios da abordagem iterativa:

  • Divide perguntas complexas em partes gerenciáveis.

  • Recupera informações mais relevantes para cada subpergunta.

  • Cria cadeias de raciocínio explícitas.

  • Permite abordar questões que seriam desafiadoras em uma única etapa.

Etapa 1: Crie uma cadeia de decomposição de consultas

A etapa de decomposição é crítica porque divide uma consulta complexa em subconsultas mais simples e focadas que podem ser respondidas 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

Etapa 2: Crie uma cadeia de atendimento de subconsultas

O componente de resposta de subconsulta lida com cada subpergunta individual, recuperando documentos relevantes e gerando respostas intermediárias focadas.

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,
)

Etapa 3: Crie uma cadeia de geração de respostas finais

O componente final de geração de respostas combina todas as respostas intermediárias para produzir uma resposta abrangente à pergunta 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

Etapa 4: Crie demonstrações de exemplo para a IterDRAG

A criação de demonstrações eficazes é crucial para o desempenho da IterDRAG. Esses exemplos mostram ao modelo como:

  1. Divida as perguntas complexas em subperguntas mais simples.

  2. Gere respostas intermediárias relevantes.

  3. Combine essas respostas em uma resposta final coerente.
@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

Etapa 5: Implemente a função da IterDRAG

Essa função orquestra todo o processo iterativo:

  1. Decomponha a pergunta principal em subperguntas.

  2. Para cada subpergunta, recupere documentos relevantes e gere uma resposta intermediária.

  3. Combine todas as respostas intermediárias para produzir a resposta final.
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}

Comparando abordagens de RAG

Agora que temos as três abordagens de RAG configuradas, vamos comparar suas respostas à mesma consulta, desta vez muito mais complexa para ver as diferenças.

A comparação nos ajudará a entender os benefícios de cada abordagem e quando cada uma pode ser mais apropriada de usar.

# 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"])

Comparação e análise de resultados

Resumimos aqui as diferenças de desempenho entre as três abordagens de RAG implementadas:

Abordagem

 

Pontos fortes

 

Limitações

 

Melhores casos de uso

 

RAG padrão

  • Implementação simples
  • Bom para consultas simples
  • Menores requisitos computacionais
  • Utilização de contexto limitada
  • O desempenho fica estagnado com mais documentos
  • Fraco em raciocínio complexo 
  • Consultas factuais simples
  • Quando a computação é limitada
  • Quando o contexto é pequeno 

DRAG

  • Melhor utilização do contexto
  • Melhor desempenho com mais documentos
  • Bom para consultas moderadamente complexas
  • Ainda limitado pela geração em uma etapa
  • Menos eficaz para perguntas com vários saltos
  • Consultas de complexidade moderada
  • Quando houver mais documentos disponíveis
  • Quando exemplos contextualizados podem ser fornecidos

IterDRAG

  • Ideal para consultas complexas
  • Cadeias de raciocínio explícitas
  • Uso mais eficaz do contexto
  • Os maiores requisitos computacionais
  • Implementação mais complexa
  • Perguntas com vários saltos
  • Análises complexas que exigem raciocínio composto
  • Quando o desempenho máximo é necessário 
    

Como vimos em nossa implementação, técnicas de dimensionamento de inferência, como DRAG e IterDRAG, podem melhorar significativamente o desempenho da RAG. Esse método é especialmente verdadeiro para consultas complexas que exigem análise profunda de vários documentos.

Conclusão

Neste tutorial, exploramos como o ajuste de escala de inferência pode melhorar drasticamente o desempenho da RAG. Ao alocar estrategicamente computação adicional no momento da inferência por meio de técnicas como DRAG e IterDRAG, podemos obter ganhos substanciais na qualidade de resposta para consultas complexas.

Desafios com os modelos tradicionais baseados em RAG e em transformadores

Inferência cara: os modelos baseados em transformadores, que usam mecanismos de autoatenção, têm custos de inferência que escalam quadraticamente com o comprimento da entrada. Esse método torna o tratamento de contextos longos computacionalmente caro, limitando a aplicação prática da RAG a documentos mais curtos ou exigindo truncamento agressivo.

Utilização limitada de contexto: os sistemas de RAG padrão geralmente recuperam e processam um número fixo de documentos que podem ser insuficientes para consultas complexas de vários saltos. O desempenho fica estagnado à medida que o comprimento do contexto aumenta, especialmente além de 128.000 tokens, porque o modelo luta para sintetizar informações em muitas passagens recuperadas.

Alocação de computação ineficiente: sem alocação cuidadosa, adicionar mais documentos recuperados ou contexto simplesmente aumenta o custo computacional sem ganhos proporcionais na precisão, levando a retornos decrescentes ou até mesmo desempenho degradado devido à sobrecarga de informações.

Como a DRAG e a IterDRAG lidam com esses desafios

RAG baseada em demonstrações (DRAG):

A DRAG aproveita vários exemplos recuperados, perguntas e respostas como demonstrações dentro do prompt, permitindo que o modelo aprenda no contexto como localizar e aplicar informações relevantes.

Essa abordagem é particularmente eficaz para contextos efetivos mais curtos, pois permite que o modelo utilize contexto rico sem sobrecarregar o mecanismo de atenção, melhorando a qualidade da recuperação e da geração.

RAG baseada em demonstração iterativa (IterDRAG):

A IterDRAG decompõe consultas complexas em subconsultas mais simples, recuperando e gerando respostas de forma iterativa para cada subetapa.

Ao intercalar recuperação e geração, a IterDRAG cria cadeias de raciocínio que preenchem a lacuna das consultas com vários saltos, tornando-o especialmente eficaz para contextos excepcionalmente longos.

Esse processo permite que o modelo aloque computação de forma mais eficiente, focando nas informações mais relevantes em cada etapa e evitando o risco de sobrecarga de atenção de contexto longo. Ao aplicar essas técnicas de dimensionamento de inferência às suas aplicações de RAG, você pode alcançar um desempenho significativamente melhor em tarefas de conhecimento intensivo sem alterar seus modelos subjacentes.

Próximas etapas:

  • Experimente com diferentes modelos de recuperação e abordagens de pré-processamento de documentos.

  • Experimente diferentes formulações de prompts para compreensão da imagem.

  • Explore a otimização de parâmetros do modelo para encontrar as configurações ideais para seu caso de uso específico.
Soluções relacionadas
IBM watsonx.ai

Treine, valide, ajuste e implemente recursos de IA generativa, modelos de base e recursos de aprendizado de máquina com o IBM watsonx.ai, um estúdio empresarial de última geração para construtores de IA. Crie aplicações de IA em uma fração do tempo com uma fração dos dados.

Conheça o watsonx.ai
Soluções de inteligência artificial

Use a IA a serviço de sua empresa com a experiência e o portfólio de soluções líder do setor da IBM à sua disposição.

Explore as soluções de IA
Consultoria e serviços em IA

Reinvente os fluxos de trabalho e operações críticos adicionando IA para maximizar experiências, tomadas de decisão em tempo real e valor de negócios.

Explore os serviços de IA
Dê o próximo passo

Obtenha acesso completo aos recursos que abrangem o ciclo de vida do desenvolvimento da IA. Produza soluções poderosas de IA com interfaces fáceis de usar, fluxos de trabalhos e acesso a APIs e SDKs padrão do setor.

Explore o watsonx.ai Agende uma demonstração em tempo real
Notas de rodapé

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 fevereiro 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.