Engenharia · 7 min de leitura

Como tiramos 368 milissegundos de silêncio de uma conversa

O reconhecimento de fala não ficou mais rápido. A gente só parou de esperar por ele, e descobriu que o custo era zero desde sempre.

O reconhecimento de fala não ficou mais rápido. A gente só parou de esperar por ele, e o custo virou zero.

Toda conversa por voz tem um orçamento de tempo, e ele é implacável: da última sílaba que a pessoa diz até a primeira que ela ouve de volta. Acima de um segundo, a conversa começa a parecer um rádio amador. Acima de dois, as pessoas interrompem por cima porque acham que a linha caiu.

O nosso orçamento estava em 3.184 ms. Ele está em 1.217 ms. Este texto é sobre a parte mais barata dessa conta, os 368 ms que sumiram sem ninguém otimizar coisa nenhuma.

O caminho de sempre

A arquitetura óbvia é uma fila indiana. Detecta que a pessoa parou de falar, manda o áudio para o reconhecimento, recebe o texto, pensa, sintetiza, toca:

detecção  ████████                          500 ms
reconhecimento    ██████                    368 ms
raciocínio              ████████████████   1907 ms
síntese                                 ███ 409 ms
                                            ─────────
                                            3184 ms

Cada bloco espera o anterior. É correto, é simples de depurar e é lento.

A pergunta que ninguém tinha feito

A detecção de fim de fala funciona por energia: enquanto o nível do áudio ficar abaixo de um limiar por tempo suficiente, considera-se que a frase acabou. Esse “tempo suficiente” era 500 ms.

Aqui está o detalhe: durante esses 500 ms de silêncio, o processador não está fazendo nada. Ele está literalmente contando. E o áudio da frase já está inteiro no buffer desde o primeiro desses milissegundos.

Então por que esperar o contador terminar para começar a reconhecer?

Reconhecimento especulativo

A mudança cabe em uma frase: aos 200 ms de silêncio, dispare o reconhecimento com o que já está no buffer. Continue contando em paralelo. Quando o contador fechar em 400 ms, o texto já voltou.

detecção  ██████                            400 ms
reconhecimento  ████                          0 ms percebido
raciocínio            ████                   460 ms
síntese                   █                   85 ms
                                            ─────────
                                             ~930 ms

“Especulativo” porque a aposta pode dar errado: a pessoa pode voltar a falar depois de 300 ms de pausa, e aí o reconhecimento que disparamos vale pouco. Descartamos e refazemos.

Vale a pena? Na nossa distribuição de pausas, a aposta acerta em cerca de nove de cada dez frases. O custo do erro é uma chamada de reconhecimento desperdiçada, barata. O ganho do acerto é 368 ms de silêncio a menos, em toda frase.

O ajuste que ainda dói

Reduzimos também o contador de 500 para 400 ms. Isso é tempo morto puro e parece de graça, mas não é: quanto menor o valor, mais cedo o sistema decide que você terminou. Quem pausa para pensar no meio da frase é cortado.

Foi o único parâmetro que testamos com pessoas em vez de com gravações. Em 300 ms, quase todo mundo era interrompido pelo menos uma vez numa conversa de dois minutos. Em 400, quase ninguém. Deixamos configurável, é o único ajuste desta lista que se sente na ligação.

O que a medição destruiu

No mesmo esforço, duas hipóteses nossas foram para o lixo:

  • “O prompt está grande demais, por isso demora.” Medimos: 5.983 caracteres deram 578 ms; 3.714 caracteres deram 650 ms. Ou seja, o prompt menor foi mais lento, o que só quer dizer que o tamanho não é a variável. Encurtar o prompt teria degradado o comportamento do agente em troca de nada.
  • “O handshake da conexão custa caro.” Custava 6 ms. Manter a conexão viva ainda valeu a pena para a síntese, abrir um cliente por frase pagava um handshake inteiro a cada frase, mas não era ali que estavam os segundos.

As duas hipóteses eram razoáveis. As duas estavam erradas. É por isso que a gente publica número medido e não número estimado: teria sido mais confortável não medir, e teríamos passado uma semana otimizando a coisa errada.

Onde o tempo está agora

Sobrou o raciocínio (~460 ms) e a síntese (~85 ms). O raciocínio caiu porque a primeira frase começa a ser dita enquanto o resto ainda é gerado. A síntese caiu de 370 para 85 ms quando trocamos um decodificador que precisa terminar a frase inteira antes de emitir o primeiro byte por um que emite enquanto gera.

Mas isso é assunto de outro texto.

Ver todos os textosTestar o VoiceSpeak

Números medidos, não estimados

Todo número que aparece aqui saiu de um teste que a gente refaz quando alguma coisa muda. Se algum estiver errado, queremos saber.