O que é Paralelizar Etapas Independentes?
Paralelizar Etapas Independentes (Parallelize Independent Steps) é uma refatoração de IA para sistemas com LLM, do grupo Movendo controle. Rodar ao mesmo tempo as chamadas que não dependem uma da outra.
Parallelize Independent Steps
Paralelizar Etapas Independentes (Parallelize Independent Steps) é uma refatoração de IA para sistemas com LLM, do grupo Movendo controle. Rodar ao mesmo tempo as chamadas que não dependem uma da outra.
Chamadas que não dependem umas das outras rodam em fila, e o usuário espera a soma de todas. É o smell Fila Indiana de Chamadas.
Imagine um copiloto para atendentes de banco. Quando o cliente manda uma mensagem no WhatsApp, o sistema prepara quatro coisas antes de sugerir a resposta: a intenção da mensagem, um resumo do histórico do cliente no CRM, a política que se aplica ao caso e o sentimento do cliente. Cada uma é uma chamada de modelo. O código chama uma, espera, chama a próxima, espera.
Nenhuma dessas quatro precisa do resultado da outra. Todas leem só a conversa ou o histórico. Mesmo assim, o atendente fica olhando um spinner enquanto elas terminam uma depois da outra.
Desenhe o grafo de dependências do pipeline e rode ao mesmo tempo todos os passos que não esperam resultado de outro. A latência passa a ser a do caminho mais longo, não a soma.
-intent = await classify_intent(transcript)
-customer = await summarize_customer(crm_history)
-policy = await find_policy(transcript)
-sentiment = await detect_sentiment(transcript)
+intent, customer, policy, sentiment = await asyncio.gather(
+ classify_intent(transcript),
+ summarize_customer(crm_history),
+ find_policy(transcript),
+ detect_sentiment(transcript),
+)
reply = await generate_reply(intent, customer, policy, sentiment)
Antes: quatro passos em fila.
flowchart LR
IN(["1. Entrada"]):::io --> A(["Intenção"]):::llm
A --> B(["Cliente"]):::llm
B --> C(["Política"]):::llm
C --> D(["Sentimento"]):::llm
D --> R(["2. Resposta"]):::llm
Depois: os quatro ao mesmo tempo, e a resposta quando o último termina.
flowchart LR
IN(["1. Entrada"]):::io --> A(["Intenção"]):::llm
IN --> B(["Cliente"]):::llm
IN --> C(["Política"]):::llm
IN --> D(["Sentimento"]):::llm
A --> R(["2. Resposta"]):::llm
B --> R
C --> R
D --> R
Suponha estas durações, medidas no trace do copiloto (números ilustrativos):
| Passo | Duração | Depende de |
|---|---|---|
| Intenção | 0,8 s | conversa |
| Cliente | 2,4 s | histórico do CRM |
| Política | 1,5 s | conversa |
| Sentimento | 0,6 s | conversa |
| Resposta | 1,8 s | os quatro acima |
Em fila, o atendente espera 0,8 + 2,4 + 1,5 + 0,6 + 1,8 = 7,1 segundos. Em paralelo, a primeira fase dura o tempo do passo mais lento (2,4 s) e depois vem a resposta (1,8 s). Total: 4,2 segundos.
O número de chamadas é o mesmo, o número de tokens é o mesmo e o texto final é o mesmo. Só o relógio mudou.
Uma chamada a modelo passa quase todo o tempo esperando o provedor gerar tokens. O processo que chamou fica parado. Enquanto espera uma, ele pode disparar as outras. Para passos independentes, não há razão de ordem: o resultado é o mesmo, e o relógio corre uma vez só.
Esse tipo de espera é o caso clássico de trabalho limitado por I/O (entrada e saída). Você não precisa de mais CPU nem de threads extras. Um asyncio.gather em Python ou um Promise.all em TypeScript bastam, porque o programa só está esperando a rede.
O ganho tem um limite claro: o caminho crítico. Se o passo mais lento leva 2,4 segundos, a fase paralela leva perto disso, não menos. Por isso, depois de paralelizar, o próximo alvo é o passo mais lento: um modelo menor, uma entrada menor ou cache.
A ideia aparece com nome próprio no post “Building effective agents” da Anthropic (dezembro de 2024), como o workflow de paralelização. Ele tem duas variantes. Em Seccionamento, cada chamada cuida de uma parte diferente da tarefa, como no copiloto. Em Votação, várias chamadas fazem a mesma tarefa e você compara as respostas. Quando a mesma operação se repete sobre muitos itens, o padrão é Map-Reduce.
asyncio.gather, Promise.all, ou o recurso do seu framework).Quem começa costuma olhar só o código. Mas às vezes a dependência mora no texto. Imagine que o prompt de find_policy diz “considere a intenção identificada acima”, e o código monta esse prompt com a variável intent. No código em fila, funcionava. No paralelo, intent ainda não existe quando a política começa.
Para pegar isso, faça a lista de dependências a partir das variáveis que entram em cada prompt, não a partir da ordem em que os passos aparecem no arquivo.
asyncio.gather padrão, o primeiro erro sobe para quem chamou e você perde os outros resultados, mesmo que as outras chamadas continuem rodando e sendo cobradas. Promise.all se comporta igual. Se o pipeline pode seguir sem um dos passos, use return_exceptions=True ou Promise.allSettled e trate cada resultado.A conta do rate limit é simples. Se o copiloto atende 50 conversas ao mesmo tempo e cada uma dispara quatro chamadas juntas, o pico vira 200 chamadas simultâneas, contra 50 antes. O consumo total por minuto não muda, mas os picos ficam quatro vezes mais altos. É o pico que bate no limite do provedor.
Quais destes passos de um revisor de contratos podem rodar em paralelo?
1. extrair_texto(pdf)
2. identificar_partes(texto)
3. listar_clausulas(texto)
4. classificar_risco(cada cláusula)
5. checar_partes_em_lista_restritiva(partes)
6. escrever_parecer(riscos, checagem)
Depois do passo 1, os passos 2 e 3 rodam em paralelo, porque ambos leem só o texto. O passo 4 roda em paralelo por cláusula (map) e ao mesmo tempo que o passo 5, que só depende das partes. O passo 6 espera 4 e 5.
texto = extrair_texto(pdf)
parallel { partes = identificar_partes(texto); clausulas = listar_clausulas(texto) }
parallel {
riscos = parallel_map(classificar_risco, clausulas)
checagem = checar_partes_em_lista_restritiva(partes)
}
parecer = escrever_parecer(riscos, checagem)Um detalhe: o passo 5 só precisa das partes, não das cláusulas. Se identificar as partes for bem mais rápido que listar as cláusulas, você pode começar a checagem assim que as partes chegarem, sem esperar o passo 3. É o mesmo princípio aplicado com mais precisão: cada passo começa quando as entradas dele estão prontas.
Paralelizar Etapas Independentes (Parallelize Independent Steps) é uma refatoração de IA para sistemas com LLM, do grupo Movendo controle. Rodar ao mesmo tempo as chamadas que não dependem uma da outra.
Trata Fila Indiana de Chamadas.
Ao padrão de projeto Seccionamento e Map-Reduce de Documentos.