Introduzione
Il nostro prodotto vocale presentava due reclami che non riuscivamo a conciliare internamente. Dai clienti aziendali di lingua inglese, l'intelligenza artificiale è sembrata scattante. Dai clienti di lingua mandarina – e sempre più da clienti di lingua vietnamita, tagalog e hindi – l’intelligenza artificiale sembrava lenta. Il divario, misurato end-to-end dal silenzio del chiamante alla prima sillaba dell’IA, è stato di 260 ms in inglese e 540 ms in mandarino. Entrambi i numeri rientravano nel budget di latenza pubblicato.
Solo uno di loro si sentiva accettabile durante una telefonata.
L'istinto di qualsiasi team di ingegneri che vede un gap di latenza è quello di perfezionare il modello. Inferenza più rapida. Modello più piccolo. Migliore quantizzazione. Abbiamo fatto tutto questo. Ci ha fruttato 60 ms su tutta la linea, il che non ha chiuso nulla: il divario relativo era ancora lì e l’inglese è rimasto due volte più veloce.
La vera soluzione è arrivata da un'osservazione diversa: trattavamo la latenza come un problema modello quando in realtà era un problema di routing.
Dove vanno effettivamente i millisecondi
Suddividere in segmenti la risposta in mandarino da 540 ms è stata la prima cosa che ha chiarito il problema. La contabilità era più o meno questa:
- Acquisizione ed endpointing dell'audio — 80 ms
- Primo token di sintesi vocale — 140 ms
- Intento + pipeline di recupero — 90 ms
- Primo token modello grande (generazione) — 180ms
- Primo audio della sintesi vocale — 50 ms
Nel percorso inglese, gli stessi segmenti arrivavano a 80 / 60 / 90 / 80 / 40. I due segmenti che divergevano notevolmente erano il parlato in testo e la generazione di modelli di grandi dimensioni. STT era più lento in mandarino perché il modello acustico era stato addestrato con una finestra di contesto più lunga – necessaria per la disambiguazione tonale – che spingeva il primo token di 80 ms.
La generazione del modello è stata più lenta perché il tokenizzatore ha prodotto più token per carattere di significato equivalente in mandarino che in inglese.
Nessuno dei due era un difetto del modello. Entrambi erano compromessi accumulati silenziosamente da team indipendenti che ottimizzavano i propri parametri: STT per l'accuratezza, LLM per la qualità della generazione. Il costo della latenza era reale, ma non era il profitto di nessuno.
La correzione del routing che ha acquistato 200 ms
Abbiamo smesso di instradare tutte le lingue attraverso un singolo LLM. Invece, abbiamo creato un sottile strato di classificazione davanti alla generazione che rileva, in meno di 8 ms, tre cose: la lingua dell'espressione in arrivo, la classe dell'intento conversazionale e se la richiesta è uno dei circa 40 modelli ad alta frequenza che abbiamo identificato raggruppando segmenti di chiamata da 2 milioni.
Quando il classificatore identifica un modello ad alta frequenza in una lingua diversa dall'inglese, indirizziamo la generazione a un modello più piccolo, specializzato nella lingua, che gestisce direttamente quel modello. Il modello più piccolo è stato distillato su milioni di completamenti dello stesso modello, quindi la sua qualità su quei percorsi specifici rientra nel rumore del modello più grande, ma la sua latenza del primo token è circa un terzo.
Quando il classificatore rileva un intento di coda (qualcosa che non rientra nei 40 modelli) la richiesta passa al modello grande, proprio come prima. La maggior parte del calo di latenza deriva dal fatto che i modelli ad alta frequenza lo sono Anche i modelli che rappresentano circa il 78% del volume delle chiamate. I casi di coda pagano la latenza originale, ma sono rari.
Il classificatore che nessuno si aspettava
Il classificatore sottile era la parte del progetto per la quale nessuno aveva previsto un budget. Costruirlo ha richiesto più tempo che costruire il sistema di routing stesso, perché le modalità di fallimento erano sottili. Un classificatore che indirizza erroneamente l'1% delle richieste a un modello di piccole dimensioni che non le gestisce produce allucinazioni, non solo risposte degradate.
Tre cose facevano funzionare il classificatore. Innanzitutto, è stato addestrato sul traffico di produzione reale, non sui dati sintetici. In secondo luogo, ha restituito un punteggio di confidenza che il router può superare: qualsiasi valore di confidenza inferiore a 0,91 passa automaticamente al modello grande.
In terzo luogo, lo abbiamo implementato in modalità shadow per sei settimane rispetto al gasdotto esistente, con il modello grande che forniva la verità sul campo, prima che il traffico venisse effettivamente instradato verso modelli piccoli.
La distribuzione shadow ha rilevato quattro classi di errori non rilevati dal set di test offline. Due erano facili da correggere nei dati di addestramento. Sono necessarie due regole di instradamento deliberate: tutto ciò che riguarda la verifica dell'identità, i pagamenti o la cancellazione di un appuntamento va sempre al grande modello indipendentemente dalla fiducia del classificatore, perché il costo di un'allucinazione su quei percorsi è superiore alla vittoria della latenza.
Il lato TTS del problema
Mentre le modifiche LLM e STT venivano esaminate, un team parallelo stava lavorando sull'output vocale. Il primo audio TTS da 50 ms in inglese era effettivamente imbattibile; il divario di 70 ms-90 ms sulla maggior parte delle lingue diverse dall'inglese è stato causato da modelli vocali più piccoli, caching meno aggressivo e un unico pool di GPU condiviso che dava priorità all'inglese sotto carico.
Abbiamo diviso l'infrastruttura TTS in pool bloccati in base alla lingua. Mandarin TTS ora funziona su un proprio pool, con le proprie regole di dimensionamento, su hardware vicino alle regioni da cui ha origine la maggior parte del nostro traffico Mandarin. La latenza è scesa da 70-90 ms a 45 ms entro due settimane dal cutover.
Niente di tutto questo era intelligente; si trattava di un lavoro infrastrutturale che nessuno si era preso la briga di fare perché il miglioramento marginale su ogni singola lingua non lo aveva, di per sé, giustificato.
La lezione - e ora la stiamo scrivendo internamente - è che "non abbiamo investito in questo perché nessun singolo parametro rivolto all'utente lo giustificava" è esattamente il tipo di decisione che si traduce in un divario di 280 ms tra due lingue in quattro anni.
Misurare la parità, non le medie
Una piccola ma significativa modifica che abbiamo apportato internamente riguardava il modo in cui viene riportato il dashboard sulla latenza del team. In passato monitoravamo i valori p50 e p95 della latenza end-to-end, calcolati in media su tutte le lingue, rispetto a un singolo SLO. Il cruscotto sembrava verde per la maggior parte del tempo.
La nuova dashboard riporta p50 e p95 per lingua contro a parità SLO: il divario tra la lingua supportata più lenta e quella più veloce ha un proprio budget, applicato separatamente. Impostiamo il budget di parità a 60 ms. Quando il divario supera il budget, il reperibile viene chiamato.
Il dashboard di parità fa qualcosa che quello basato sulla media non potrebbe fare: rende visibili le regressioni nella lingua più lenta con la stessa urgenza delle regressioni in quella più veloce. Quando si calcola la media su 32 lingue, una regressione di 200 ms in vietnamita sposta la media di 6 ms. Quando misuri la parità, la dashboard si sposta di 200 ms.
Ciò che è ancora lento
Dopo il lavoro di routing, i pool TTS e il dashboard di parità, la nostra lingua peggiore (il cantonese, principalmente perché il corpus del modello acustico di supporto è più piccolo) si attesta a 320 ms. L'inglese è a 220ms. Il restante divario di 100 ms è reale e sappiamo più o meno dove si trova: il modello acustico cantonese STT è più vecchio e non è ancora sulla nuova architettura, e lo riqualificheremo nel terzo trimestre.
Ma "la lingua più lenta è 100 ms indietro rispetto alla più veloce" è una conversazione diversa da "la lingua più lenta è 280 ms indietro rispetto alla più veloce". La prima è una regressione nota con una correzione trimestrale pianificata. Quest'ultima era un'emergenza relativa all'esperienza del cliente che non sapevamo di avere.
Cosa faremmo diversamente la prossima volta
Tre cose, in ordine di priorità, se stessimo ricostruendo lo stack di latenza da zero con ciò che sappiamo ora.
- Costruisci prima il classificatore-router, poi il modello. Il livello di routing ha finito per essere l’elemento con la leva più alta. Considerarlo come un "lo aggiungeremo più tardi se ne avremo bisogno" si è rivelato significare che abbiamo convissuto con la latenza del modello singolo per 18 mesi in più del necessario.
- Imposta gli SLO di parità prima degli SLO della lingua. La tentazione è quella di fissare obiettivi per lingua e lasciare emergere la parità. La parità non emerge; diverge, lentamente, nella direzione della lingua principale della squadra.
- Fornisci risorse all'infrastruttura per lingua come se ogni lingua fosse un prodotto separato. Mandarin TTS meritava il proprio pool di GPU fin dal primo giorno, non dal giorno in cui abbiamo misurato il gap di latenza.
Niente di tutto questo è una nuova ricerca. Niente di tutto ciò richiedeva un documento. È stato necessario considerare la parità di latenza come un impegno per il prodotto, quindi finanziare l’infrastruttura poco affascinante per sostenere l’impegno. Abbiamo la parità di cui abbiamo bisogno adesso. Non l'abbiamo avuto per quattro anni.