O BreezeRT mantém baixa a latência do primeiro pacote do Breeze TTS 2 sob alta concorrência. Reduzimos a espera no escalonamento de solicitações, na divisão do áudio em blocos e na execução na GPU, e usamos o Predictor para escalonar o primeiro áudio e os blocos seguintes. Novas solicitações começam rapidamente, enquanto a fala em andamento continua sendo reproduzida.
Visão geral das otimizações do BreezeRT
| Otimização | Efeito |
|---|---|
| 1. Escalonamento unificado de múltiplas etapas | Menos espera |
| 2. Blocos progressivos e decodificação incremental | TTFB da primeira solicitação: ~2500 → 150 ms |
| 3. Compilação do Depth e CUDA Graphs | TTFB da primeira solicitação: ~150 → 100 → 35 ms |
| 4. Predictor: prazos do primeiro áudio e da reprodução | Solicitações com possíveis pausas em um total de 300: 14 → 0 |
Comparação de frameworks de inferência
Todos os testes deste artigo foram realizados em uma H100. Comparamos PyTorch, vLLM-Omni, SGLang-Omni e BreezeRT com o mesmo modelo Breeze TTS 2, com 1–16 solicitações simultâneas, focando na latência do primeiro pacote.

Adaptamos e ajustamos PyTorch, vLLM-Omni e SGLang-Omni para o Breeze TTS 2. O gráfico mostra os melhores resultados que medimos com cada framework. Nossas adaptações ainda não foram incorporadas aos respectivos repositórios de código aberto.
1. Escalonamento unificado de múltiplas etapas
O Breeze TTS 2 primeiro codifica o texto e executa o Backbone Prefill. Para cada quadro de fala, o Backbone prevê o primeiro código de áudio, o Depth Decoder gera os 15 códigos restantes e o Codec produz as amostras da forma de onda. Cada etapa tem diferentes requisitos de processamento, frequências de execução e tamanhos de lote adequados.
Solicitações simultâneas costumam estar em etapas diferentes: A tem códigos de áudio prontos para decodificar, B está gerando seu próximo quadro e C acabou de chegar e espera pelo primeiro áudio. Continuar com A deixaria B e C esperando. O escalonador precisa alternar entre solicitações antes que uma frase inteira seja concluída.
O BreezeRT escalona separadamente o processamento de texto, Backbone Prefill, Backbone Decode, Depth e a decodificação de áudio. Um único ambiente de execução gerencia o progresso das solicitações, os caches e o áudio pendente. Após cada etapa, escolhe qual etapa e quais solicitações executar em seguida.

O escalonador seleciona solicitações por prioridade e depois agrupa o trabalho em lotes dentro da etapa escolhida. Cada etapa tem seu próprio limite de lote, permitindo que Codec e Backbone usem tamanhos de lote diferentes. Novas solicitações podem entrar entre etapas, e fluxos ativos recebem tempo para decodificar. O mesmo ambiente de execução cuida da divisão em blocos, da execução de grafos capturados e do escalonamento por prazos.
2. Blocos progressivos e decodificação incremental
Um primeiro bloco maior faz o usuário esperar por mais quadros antes de ouvir áudio. Porém, enviar um quadro por vez exige executar o Codec e o escalonador com mais frequência. Esse custo se acumula à medida que as solicitações aumentam.
O BreezeRT aumenta gradualmente o tamanho dos blocos, usando, por exemplo, 1, 2, 4, 8 e 16 quadros. Blocos pequenos iniciam a reprodução rapidamente. Quando já há algum áudio no buffer, blocos maiores reduzem o número de chamadas de decodificação.
O Codec também precisa lembrar o trabalho anterior para não decodificar o mesmo áudio novamente. O BreezeRT armazena, para cada solicitação, os KV do Transformer com janela deslizante, o histórico de convolução causal e a cauda de sobreposição da convolução transposta, e então processa apenas os novos quadros. Esses caches preservam o contexto e suavizam as transições entre blocos quando seus tamanhos mudam.
Na comparação de apoio, a primeira solicitação recebeu áudio após cerca de 2500 ms com um primeiro bloco fixo de 16 quadros e cerca de 150 ms com blocos progressivos.
3. Compilação do Depth e CUDA Graphs
Com um primeiro bloco menor, o custo de lançamento pela CPU fica mais evidente. Gerar um quadro de fala exige muitas operações do modelo. Mesmo uma GPU rápida pode ficar ociosa entre envios. O BreezeRT reduz o custo de processamento por meio de compilação e usa CUDA Graphs para enviar operações de GPU capturadas em uma única execução do grafo.
A execução de CUDA Graphs exige formatos fixos de tensores, mas os comprimentos de texto, os tamanhos de lote e os tamanhos dos blocos de áudio variam. Capturar todas as combinações consumiria memória demais. O BreezeRT mantém um conjunto de formatos comuns e divide ou preenche as entradas para ajustá-las a eles.
O Text Encoder compacta tokens e divide o trabalho por quantidade de solicitações e capacidade de tokens para reduzir o preenchimento. A atenção permanece separada para cada solicitação. O Backbone Prefill trabalha em blocos de tokens, com KV paginados armazenando o histórico de geração de cada solicitação para reutilizar grafos e buffers existentes.

O Depth Decoder gera mais 15 códigos de áudio por quadro. O BreezeRT compila a pilha do decodificador e as cabeças de saída como unidades completas, fundindo operações compatíveis elemento a elemento em menos kernels. Isso reduz leituras e gravações de tensores intermediários e lançamentos de kernels, um trabalho repetido que se acumula entre solicitações simultâneas. Os buffers de KV, posição e entrada/saída são alocados antecipadamente. O laço autorregressivo completo é capturado em um único grafo, de modo que uma execução realiza todos os 15 passos na ordem de dependência.

A codificação de texto, Backbone, Depth e a decodificação de áudio usam grafos, com os formatos comuns aquecidos na inicialização. Cada solicitação mantém suas próprias configurações de amostragem e estado aleatório. Sem alterar a divisão em blocos, a comparação de apoio mediu uma latência da primeira solicitação de cerca de 150 ms, que caiu para 100 ms com a compilação do Depth e 35 ms com grafos em todas as etapas.
4. Predictor: prazos do primeiro áudio e da reprodução
Novas solicitações esperam para começar a falar; fluxos ativos esperam pelo próximo bloco. Atender sempre às novas solicitações primeiro pode interromper a reprodução. Gerar com antecedência excessiva para fluxos ativos faz as novas solicitações esperarem. O Predictor usa prazos de reprodução para decidir qual trabalho vem primeiro.
Novas solicitações recebem prioridade de início. Para fluxos ativos, o momento da primeira saída e a duração total do áudio fornecem uma estimativa do prazo de reprodução. O Predictor registra os tempos recentes das etapas por tamanho de lote e inclui uma margem ao estimar se o trabalho restante de Backbone, Depth e Codec pode terminar a tempo.
Por exemplo, A está pronta para decodificar, enquanto B precisa de mais um passo de geração para se juntar ao seu lote. O escalonador espera por B se o processamento em lote economizar tempo e ainda cumprir o prazo de reprodução de A. Caso contrário, A é executada primeiro. Se o bloco desejado não puder ser concluído a tempo, o ambiente de execução pode enviar os quadros já gerados para evitar que o player fique esperando.
O Predictor usa uma estimativa simples de tempo. Em uma comparação com 300 solicitações, as solicitações com possíveis pausas na reprodução caíram de 14 para 0. O buffer adicional máximo necessário para evitar uma pausa caiu de cerca de 330 ms para 0 ms.
