trustgraph/docs/tech-specs/embeddings-batch-processing.pt.md

676 lines
22 KiB
Markdown
Raw Normal View History

---
layout: default
title: "Especificação Técnica de Processamento em Lote de Embeddings"
parent: "Portuguese (Beta)"
---
# Especificação Técnica de Processamento em Lote de Embeddings
> **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
## Visão Geral
Esta especificação descreve otimizações para o serviço de embeddings para suportar o processamento em lote de vários textos em um único pedido. A implementação atual processa um texto por vez, perdendo os benefícios de desempenho significativos que os modelos de embedding oferecem ao processar lotes.
1. **Ineficiência no Processamento de Texto Único**: A implementação atual envolve textos únicos em uma lista, subutilizando as capacidades de lote do FastEmbed.
2. **Sobrecarga de Pedido por Texto**: Cada texto requer uma viagem de ida e volta separada do Pulsar.
3. **Ineficiência na Inferência do Modelo**: Os modelos de embedding têm uma sobrecarga fixa por lote; lotes pequenos desperdiçam recursos de GPU/CPU.
4. **Processamento Serial nos Clientes**: Serviços importantes fazem loop sobre itens e chamam embeddings um de cada vez.
## Objetivos
**Suporte para API de Lote**: Permitir o processamento de vários textos em um único pedido.
**Compatibilidade com Versões Anteriores**: Manter o suporte para pedidos de texto único.
**Melhora Significativa no Desempenho**: Almejar uma melhora de desempenho de 5 a 10 vezes para operações em lote.
**Latência Reduzida por Texto**: Diminuir a latência amortizada ao incorporar vários textos.
**Eficiência de Memória**: Processar lotes sem consumo excessivo de memória.
**Independente do Provedor**: Suportar o processamento em lote em FastEmbed, Ollama e outros provedores.
**Migração do Cliente**: Atualizar todos os clientes de embedding para usar a API de lote sempre que for benéfico.
## Contexto
### Implementação Atual - Serviço de Embeddings
A implementação de embeddings em `trustgraph-flow/trustgraph/embeddings/fastembed/processor.py` apresenta uma ineficiência de desempenho significativa:
```python
# fastembed/processor.py line 56
async def on_embeddings(self, text, model=None):
use_model = model or self.default_model
self._load_model(use_model)
vecs = self.embeddings.embed([text]) # Single text wrapped in list
return [v.tolist() for v in vecs]
```
**Problemas:**
1. **Tamanho do Lote 1**: O método `embed()` do FastEmbed é otimizado para processamento em lote, mas sempre o chamamos com `[text]` - um lote de tamanho 1.
2. **Sobrecarga por Requisição**: Cada solicitação de incorporação incorre em:
Serialização/desserialização de mensagens Pulsar
Latência de ida e volta da rede
Sobrecarga de inicialização da inferência do modelo
Sobrecarga de agendamento assíncrono do Python
3. **Limitação do Esquema**: O esquema `EmbeddingsRequest` suporta apenas um único texto:
```python
@dataclass
class EmbeddingsRequest:
text: str = "" # Single text only
```
### Clientes Atuais - Processamento Serial
#### 1. Gateway de API
**Arquivo:** `trustgraph-flow/trustgraph/gateway/dispatch/embeddings.py`
O gateway aceita solicitações de incorporação de texto único via HTTP/WebSocket e as encaminha para o serviço de incorporação. Atualmente, não existe um endpoint para processamento em lote.
```python
class EmbeddingsRequestor(ServiceRequestor):
# Handles single EmbeddingsRequest -> EmbeddingsResponse
request_schema=EmbeddingsRequest, # Single text only
response_schema=EmbeddingsResponse,
```
**Impacto:** Clientes externos (aplicativos web, scripts) devem fazer N requisições HTTP para incorporar N textos.
#### 2. Serviço de Incorporação de Documentos
**Arquivo:** `trustgraph-flow/trustgraph/embeddings/document_embeddings/embeddings.py`
Processa blocos de documentos um de cada vez:
```python
async def on_message(self, msg, consumer, flow):
v = msg.value()
# Single chunk per request
resp = await flow("embeddings-request").request(
EmbeddingsRequest(text=v.chunk)
)
vectors = resp.vectors
```
**Impacto:** Cada trecho de documento requer uma chamada de embedding separada. Um documento com 100 trechos = 100 requisições de embedding.
#### 3. Serviço de Embeddings de Grafos
**Arquivo:** `trustgraph-flow/trustgraph/embeddings/graph_embeddings/embeddings.py`
Percorre as entidades e incorpora cada uma sequencialmente:
```python
async def on_message(self, msg, consumer, flow):
for entity in v.entities:
# Serial embedding - one entity at a time
vectors = await flow("embeddings-request").embed(
text=entity.context
)
entities.append(EntityEmbeddings(
entity=entity.entity,
vectors=vectors,
chunk_id=entity.chunk_id,
))
```
**Impacto:** Uma mensagem com 50 entidades = 50 solicitações de incorporação serial. Isso é um grande gargalo durante a construção do grafo de conhecimento.
#### 4. Serviço de Incorporação de Linhas
**Arquivo:** `trustgraph-flow/trustgraph/embeddings/row_embeddings/embeddings.py`
Percorre textos únicos e incorpora cada um serialmente:
```python
async def on_message(self, msg, consumer, flow):
for text, (index_name, index_value) in texts_to_embed.items():
# Serial embedding - one text at a time
vectors = await flow("embeddings-request").embed(text=text)
embeddings_list.append(RowIndexEmbedding(
index_name=index_name,
index_value=index_value,
text=text,
vectors=vectors
))
```
**Impacto:** Processar uma tabela com 100 valores indexados únicos = 100 solicitações de incorporação seriais.
#### 5. EmbeddingsClient (Cliente Base)
**Arquivo:** `trustgraph-base/trustgraph/base/embeddings_client.py`
O cliente usado por todos os processadores de fluxo suporta apenas a incorporação de texto único:
```python
class EmbeddingsClient(RequestResponse):
async def embed(self, text, timeout=30):
resp = await self.request(
EmbeddingsRequest(text=text), # Single text
timeout=timeout
)
return resp.vectors
```
**Impacto:** Todos os clientes que utilizam este componente estão limitados a operações de texto único.
#### 6. Ferramentas de Linha de Comando
**Arquivo:** `trustgraph-cli/trustgraph/cli/invoke_embeddings.py`
A ferramenta de linha de comando aceita um único argumento de texto:
```python
def query(url, flow_id, text, token=None):
result = flow.embeddings(text=text) # Single text
vectors = result.get("vectors", [])
```
**Impacto:** Os usuários não podem realizar incorporações em lote a partir da linha de comando. O processamento de um arquivo de textos requer N invocações.
#### 7. SDK Python
O SDK Python fornece duas classes de cliente para interagir com os serviços da TrustGraph. Ambas suportam apenas a incorporação de um único texto.
**Arquivo:** `trustgraph-base/trustgraph/api/flow.py`
```python
class FlowInstance:
def embeddings(self, text):
"""Get embeddings for a single text"""
input = {"text": text}
return self.request("service/embeddings", input)["vectors"]
```
**Arquivo:** `trustgraph-base/trustgraph/api/socket_client.py`
```python
class SocketFlowInstance:
def embeddings(self, text: str, **kwargs: Any) -> Dict[str, Any]:
"""Get embeddings for a single text via WebSocket"""
request = {"text": text}
return self.client._send_request_sync(
"embeddings", self.flow_id, request, False
)
```
**Impacto:** Desenvolvedores Python que usam o SDK precisam iterar sobre textos e fazer N chamadas de API separadas. Não existe suporte para processamento em lote de embeddings para usuários do SDK.
### Impacto no Desempenho
Para ingestão típica de documentos (1000 trechos de texto):
**Atual:** 1000 requisições separadas, 1000 chamadas de inferência de modelo
**Em lote (batch_size=32):** 32 requisições, 32 chamadas de inferência de modelo (redução de 96,8%)
Para embedding de grafos (mensagem com 50 entidades):
**Atual:** 50 chamadas `await` sequenciais, ~5-10 segundos
**Em lote:** 1-2 chamadas em lote, ~0,5-1 segundo (melhora de 5-10x)
Bibliotecas como FastEmbed e similares alcançam escalabilidade quase linear no throughput com o tamanho do lote, até os limites do hardware (tipicamente 32-128 textos por lote).
## Design Técnico
### Arquitetura
A otimização de processamento em lote de embeddings requer alterações nos seguintes componentes:
#### 1. **Aprimoramento do Esquema**
Estender `EmbeddingsRequest` para suportar múltiplos textos
Estender `EmbeddingsResponse` para retornar múltiplos conjuntos de vetores
Manter a compatibilidade retroativa com requisições de texto único
Módulo: `trustgraph-base/trustgraph/schema/services/llm.py`
#### 2. **Aprimoramento do Serviço Base**
Atualizar `EmbeddingsService` para lidar com requisições em lote
Adicionar configuração do tamanho do lote
Implementar tratamento de requisições com suporte a lote
Módulo: `trustgraph-base/trustgraph/base/embeddings_service.py`
#### 3. **Atualizações do Processador do Provedor**
Atualizar o processador FastEmbed para passar o lote completo para `embed()`
Atualizar o processador Ollama para lidar com lotes (se suportado)
Adicionar processamento sequencial como fallback para provedores sem suporte a lote
Módulos:
`trustgraph-flow/trustgraph/embeddings/fastembed/processor.py`
`trustgraph-flow/trustgraph/embeddings/ollama/processor.py`
#### 4. **Aprimoramento do Cliente**
Adicionar método de embedding em lote para `EmbeddingsClient`
Suportar APIs de texto único e em lote
Adicionar agrupamento automático para grandes entradas
Módulo: `trustgraph-base/trustgraph/base/embeddings_client.py`
#### 5. **Atualizações do Chamador - Processadores de Fluxo**
Atualizar `graph_embeddings` para agrupar contextos de entidades
Atualizar `row_embeddings` para agrupar textos de índice
Atualizar `document_embeddings` se o agrupamento de mensagens for viável
Módulos:
`trustgraph-flow/trustgraph/embeddings/graph_embeddings/embeddings.py`
`trustgraph-flow/trustgraph/embeddings/row_embeddings/embeddings.py`
`trustgraph-flow/trustgraph/embeddings/document_embeddings/embeddings.py`
#### 6. **Aprimoramento do Gateway de API**
Adicionar endpoint de embedding em lote
Suportar array de textos no corpo da requisição
Módulo: `trustgraph-flow/trustgraph/gateway/dispatch/embeddings.py`
#### 7. **Aprimoramento da Ferramenta de Linha de Comando (CLI)**
Adicionar suporte para múltiplos textos ou entrada de arquivo
Adicionar parâmetro de tamanho do lote
Módulo: `trustgraph-cli/trustgraph/cli/invoke_embeddings.py`
#### 8. **Aprimoramento do SDK Python**
Adicionar método `embeddings_batch()` para `FlowInstance`
Adicionar método `embeddings_batch()` para `SocketFlowInstance`
Suportar APIs de texto único e em lote para usuários do SDK
Módulos:
`trustgraph-base/trustgraph/api/flow.py`
`trustgraph-base/trustgraph/api/socket_client.py`
### Modelos de Dados
#### EmbeddingsRequest
```python
@dataclass
class EmbeddingsRequest:
texts: list[str] = field(default_factory=list)
```
Uso:
Texto único: `EmbeddingsRequest(texts=["hello world"])`
Lote: `EmbeddingsRequest(texts=["text1", "text2", "text3"])`
#### EmbeddingsResponse
```python
@dataclass
class EmbeddingsResponse:
error: Error | None = None
vectors: list[list[list[float]]] = field(default_factory=list)
```
Estrutura da resposta:
`vectors[i]` contém o conjunto de vetores para `texts[i]`
Cada conjunto de vetores é `list[list[float]]` (os modelos podem retornar vários vetores por texto)
Exemplo: 3 textos → `vectors` tem 3 entradas, cada uma contendo os embeddings desse texto
### APIs
#### EmbeddingsClient
```python
class EmbeddingsClient(RequestResponse):
async def embed(
self,
texts: list[str],
timeout: float = 300,
) -> list[list[list[float]]]:
"""
Embed one or more texts in a single request.
Args:
texts: List of texts to embed
timeout: Timeout for the operation
Returns:
List of vector sets, one per input text
"""
resp = await self.request(
EmbeddingsRequest(texts=texts),
timeout=timeout
)
if resp.error:
raise RuntimeError(resp.error.message)
return resp.vectors
```
#### Endpoint de Incorporação do Gateway de API
Endpoint atualizado que suporta incorporação única ou em lote:
```
POST /api/v1/embeddings
Content-Type: application/json
{
"texts": ["text1", "text2", "text3"],
"flow_id": "default"
}
Response:
{
"vectors": [
[[0.1, 0.2, ...]],
[[0.3, 0.4, ...]],
[[0.5, 0.6, ...]]
]
}
```
### Detalhes de Implementação
#### Fase 1: Alterações no Esquema
**EmbeddingsRequest:**
```python
@dataclass
class EmbeddingsRequest:
texts: list[str] = field(default_factory=list)
```
**EmbeddingsResponse:**
```python
@dataclass
class EmbeddingsResponse:
error: Error | None = None
vectors: list[list[list[float]]] = field(default_factory=list)
```
**Atualizado EmbeddingsService.on_request:**
```python
async def on_request(self, msg, consumer, flow):
request = msg.value()
id = msg.properties()["id"]
model = flow("model")
vectors = await self.on_embeddings(request.texts, model=model)
response = EmbeddingsResponse(error=None, vectors=vectors)
await flow("response").send(response, properties={"id": id})
```
#### Fase 2: Atualização do Processador FastEmbed
**Atual (Ineficiente):**
```python
async def on_embeddings(self, text, model=None):
use_model = model or self.default_model
self._load_model(use_model)
vecs = self.embeddings.embed([text]) # Batch of 1
return [v.tolist() for v in vecs]
```
**Atualizado:**
```python
async def on_embeddings(self, texts: list[str], model=None):
"""Embed texts - processes all texts in single model call"""
if not texts:
return []
use_model = model or self.default_model
self._load_model(use_model)
# FastEmbed handles the full batch efficiently
all_vecs = list(self.embeddings.embed(texts))
# Return list of vector sets, one per input text
return [[v.tolist()] for v in all_vecs]
```
#### Fase 3: Atualização do Serviço de Incorporação de Grafos
**Atual (Serial):**
```python
async def on_message(self, msg, consumer, flow):
entities = []
for entity in v.entities:
vectors = await flow("embeddings-request").embed(text=entity.context)
entities.append(EntityEmbeddings(...))
```
**Atualizado (Lote):**
```python
async def on_message(self, msg, consumer, flow):
# Collect all contexts
contexts = [entity.context for entity in v.entities]
# Single batch embedding call
all_vectors = await flow("embeddings-request").embed(texts=contexts)
# Pair results with entities
entities = [
EntityEmbeddings(
entity=entity.entity,
vectors=vectors[0], # First vector from the set
chunk_id=entity.chunk_id,
)
for entity, vectors in zip(v.entities, all_vectors)
]
```
#### Fase 4: Atualização do Serviço de Incorporação de Dados
**Atual (Serial):**
```python
for text, (index_name, index_value) in texts_to_embed.items():
vectors = await flow("embeddings-request").embed(text=text)
embeddings_list.append(RowIndexEmbedding(...))
```
**Atualizado (Lote):**
```python
# Collect texts and metadata
texts = list(texts_to_embed.keys())
metadata = list(texts_to_embed.values())
# Single batch embedding call
all_vectors = await flow("embeddings-request").embed(texts=texts)
# Pair results
embeddings_list = [
RowIndexEmbedding(
index_name=meta[0],
index_value=meta[1],
text=text,
vectors=vectors[0] # First vector from the set
)
for text, meta, vectors in zip(texts, metadata, all_vectors)
]
```
#### Fase 5: Melhoria da Ferramenta de Linha de Comando (CLI)
**CLI Atualizado:**
```python
def main():
parser = argparse.ArgumentParser(...)
parser.add_argument(
'text',
nargs='*', # Zero or more texts
help='Text(s) to convert to embedding vectors',
)
parser.add_argument(
'-f', '--file',
help='File containing texts (one per line)',
)
parser.add_argument(
'--batch-size',
type=int,
default=32,
help='Batch size for processing (default: 32)',
)
```
Uso:
```bash
# Single text (existing)
tg-invoke-embeddings "hello world"
# Multiple texts
tg-invoke-embeddings "text one" "text two" "text three"
# From file
tg-invoke-embeddings -f texts.txt --batch-size 64
```
#### Fase 6: Melhoria do SDK Python
**FlowInstance (cliente HTTP):**
```python
class FlowInstance:
def embeddings(self, texts: list[str]) -> list[list[list[float]]]:
"""
Get embeddings for one or more texts.
Args:
texts: List of texts to embed
Returns:
List of vector sets, one per input text
"""
input = {"texts": texts}
return self.request("service/embeddings", input)["vectors"]
```
**SocketFlowInstance (cliente WebSocket):**
```python
class SocketFlowInstance:
def embeddings(self, texts: list[str], **kwargs: Any) -> list[list[list[float]]]:
"""
Get embeddings for one or more texts via WebSocket.
Args:
texts: List of texts to embed
Returns:
List of vector sets, one per input text
"""
request = {"texts": texts}
response = self.client._send_request_sync(
"embeddings", self.flow_id, request, False
)
return response["vectors"]
```
**Exemplos de uso do SDK:**
```python
# Single text
vectors = flow.embeddings(["hello world"])
print(f"Dimensions: {len(vectors[0][0])}")
# Batch embedding
texts = ["text one", "text two", "text three"]
all_vectors = flow.embeddings(texts)
# Process results
for text, vecs in zip(texts, all_vectors):
print(f"{text}: {len(vecs[0])} dimensions")
```
## Considerações de Segurança
**Limites de Tamanho da Requisição**: Impor o tamanho máximo do lote para evitar o esgotamento de recursos.
**Tratamento de Tempo Limite (Timeout)**: Ajustar os tempos limite de acordo com o tamanho do lote.
**Limites de Memória**: Monitorar o uso de memória para grandes lotes.
**Validação de Entrada**: Validar todos os textos no lote antes do processamento.
## Considerações de Desempenho
### Melhorias Esperadas
**Taxa de Transferência (Throughput):**
Texto único: ~10-50 textos/segundo (dependendo do modelo)
Lote (tamanho 32): ~200-500 textos/segundo (melhora de 5 a 10 vezes)
**Latência por Texto:**
Texto único: 50-200ms por texto
Lote (tamanho 32): 5-20ms por texto (média)
**Melhorias Específicas do Serviço:**
| Serviço | Atual | Em Lote | Melhoria |
|---------|---------|---------|-------------|
| Incorporações de Grafos (50 entidades) | 5-10s | 0,5-1s | 5-10x |
| Incorporações de Linhas (100 textos) | 10-20s | 1-2s | 5-10x |
| Ingestão de Documentos (1000 fragmentos) | 100-200s | 10-30s | 5-10x |
### Parâmetros de Configuração
```python
# Recommended defaults
DEFAULT_BATCH_SIZE = 32
MAX_BATCH_SIZE = 128
BATCH_TIMEOUT_MULTIPLIER = 2.0
```
## Estratégia de Testes
### Testes Unitários
Incorporação de texto única (compatibilidade com versões anteriores)
Tratamento de lotes vazios
Imposição do tamanho máximo do lote
Tratamento de erros para falhas parciais do lote
### Testes de Integração
Incorporação de lote de ponta a ponta através do Pulsar
Processamento de lote do serviço de incorporação de grafos
Processamento de lote do serviço de incorporação de linhas
Endpoint de lote do gateway de API
### Testes de Desempenho
Comparação de benchmark de taxa de transferência única versus lote
Uso de memória sob vários tamanhos de lote
Análise da distribuição de latência
## Plano de Migração
Esta é uma versão que introduz alterações significativas. Todas as fases são implementadas juntas.
### Fase 1: Alterações no Esquema
Substituir `text: str` por `texts: list[str]` em EmbeddingsRequest
Alterar o tipo de `vectors` para `list[list[list[float]]]` em EmbeddingsResponse
### Fase 2: Atualizações do Processador
Atualizar a assinatura de `on_embeddings` nos processadores FastEmbed e Ollama
Processar o lote completo em uma única chamada de modelo
### Fase 3: Atualizações do Cliente
Atualizar `EmbeddingsClient.embed()` para aceitar `texts: list[str]`
### Fase 4: Atualizações do Chamador
Atualizar graph_embeddings para incorporar contextos de entidades em lote
Atualizar row_embeddings para incorporar textos de índice em lote
Atualizar document_embeddings para usar o novo esquema
Atualizar a ferramenta de linha de comando (CLI)
### Fase 5: Gateway de API
Atualizar o endpoint de incorporação para o novo esquema
### Fase 6: SDK Python
Atualizar a assinatura de `FlowInstance.embeddings()`
Atualizar a assinatura de `SocketFlowInstance.embeddings()`
## Perguntas Abertas
**Incorporação de Lotes Grandes em Streaming**: Devemos suportar o streaming de resultados para lotes muito grandes (>100 textos)?
**Limites Específicos do Provedor**: Como devemos lidar com provedores com tamanhos máximos de lote diferentes?
**Tratamento de Falhas Parciais**: Se um texto em um lote falhar, devemos falhar o lote inteiro ou retornar resultados parciais?
**Incorporação em Lote de Documentos**: Devemos incorporar em lote em várias mensagens de Chunk ou manter o processamento por mensagem?
## Referências
[Documentação do FastEmbed](https://github.com/qdrant/fastembed)
[API de Incorporação do Ollama](https://github.com/ollama/ollama)
[Implementação do Serviço de Incorporação](trustgraph-base/trustgraph/base/embeddings_service.py)
[Otimização de Desempenho do GraphRAG](graphrag-performance-optimization.md)