Una guida visiva per modificare una maschera di attenzione in un modello decisionale simile a Jev
Una guida visiva per modificare una maschera di attenzione in un modello decisionale simile a Jev

Diego Fiori
·
Co-founder & CTO

In breve
Jev, il modello decisionale di TypeSafe, risponde a molteplici domande di classificazione su un singolo documento in un unico passaggio in avanti (forward pass) e restituisce probabilità calibrate anziché testo. Questa guida lo ricostruisce passo dopo passo: fermati prima della decodifica, modifica la maschera di attenzione (attention mask) e gli id di posizione (position ids) in modo che le domande rimangano isolate e indipendenti dall'ordine, sostituisci la testa LM con una testa a puntatore (pointer head) e ottimizza (fine-tune) con la cross-entropia e la scala di temperatura (temperature scaling). Nulla di tutto ciò aggiunge nuova conoscenza, quindi il backbone rappresenta ancora il limite massimo.
Questa guida ha un unico filo conduttore: la maschera di attenzione. Partiamo da ciò che fa la maschera in un comune transformer causale, la modifichiamo fino a far avvenire la classificazione in un singolo passaggio in avanti (forward pass), risolviamo i tre problemi che si presentano lungo il percorso, sostituiamo la parte finale di output e ottimizziamo (fine-tune) il risultato. Il design target è la ricostruzione black-box di Archer Hume del Jev di TypeSafe. Le misurazioni provengono dal kev di Jared Palmer, che lo implementa su Qwen2.5-0.5B, e da laya, che prende l'altro ramo del fork descritto poco sotto. Laddove si tratta ancora di inferenza anziché di misurazione, il testo lo specifica.
1. Dove si trova già la risposta
Ecco una chiamata di classificazione, nel modo in cui quasi tutti la eseguono oggi.
Ci sono due cose che non vanno, e richiedono soluzioni separate.
La confidenza è una sequenza di token. Il modello ha emesso i caratteri 0, ., 9, 1. La probabilità che il modello emetta questi caratteri non corrisponde alla probabilità che payments sia la coda corretta. Si tratta di due quantità diverse, stampate con lo stesso font. Nulla nel training le ha mai collegate.
Hai pagato per un ciclo di decode di cui non avevi bisogno. Il token t+1 non esiste finché non hai campionato il token t, quindi questi 28 passaggi sono strettamente sequenziali. E hanno prodotto informazioni che erano già presenti nel modello dopo il prefill.
Questa seconda frase è quella portante. Per capire perché è vera, dobbiamo guardare a cosa fa effettivamente la maschera.
Prima di tutto, un fork
Una decisione precede tutte le altre, perché stabilisce se le successive sei parti si applicano o meno al tuo caso.
Un decoder causale ha una maschera che deve essere modificata. Un encoder bidirezionale, come BERT e i suoi discendenti, non ne ha mai avuta una: ogni token legge già ogni altro token, ed è stato pre-addestrato per rappresentare anziché per generare. Se parti da qui, la maggior parte di questa guida non ti serve. Passa direttamente alla lettura nella parte 8.
Questa guida prende la strada del decoder, ed è l'opzione predefinita che ti consiglio. Il motivo risiede nell'unica cosa che nessuna di queste modifiche può fornire: la conoscenza. Ogni intervento descritto di seguito cambia il modo in cui la conoscenza di un modello viene letta. Nessuno di essi ne aggiunge di nuova. È nei decoder che è stata concentrata la potenza di calcolo del pre-addestramento, e non esistono encoder da 25B; i migliori si aggirano intorno ai 400M.
Questa non è una preoccupazione teorica. laya è l'implementazione basata su encoder più forte di questo design, costruita su ModernBERT-large a 421M. I suoi checkpoint di base ottengono un punteggio inferiore alla baseline della classe di maggioranza sul proprio benchmark di decisioni tipizzate in modalità zero-shot: 0,362 contro uno 0,461 della classe di maggioranza e lo 0,318 del caso casuale. Dopo il fine-tuning sul subset di training di quel benchmark, raggiungono 0,766. Il loro file README lo spiega perfettamente: una base rapida da specializzare, non un motore decisionale zero-shot.
Quindi, la strada dell'encoder è la scelta giusta quando conosci già il tuo workflow, disponi di dati etichettati e desideri una latenza di 33 ms per richiesta. La strada del decoder è la scelta giusta quando vuoi qualcosa di utile su uno schema mai visto prima, che è esattamente ciò che promette l'interfaccia di Jev. La maggior parte delle persone che lo richiedono cerca questa seconda opzione.
La strada dell'encoder ricompare due volte in seguito, nella parte 8 e nella parte 10, perché su due questioni specifiche ha misurato il costo di una scelta di design che questa guida imposta diversamente.
2. Cosa fa effettivamente una maschera di attenzione
All'interno di un layer di attenzione, ogni token viene proiettato in una query, una chiave e un valore. Ogni query viene confrontata con ogni chiave, fornendo una matrice quadrata di punteggi.
La riga i indica quanto il token i vuole leggere da ogni altro token. Applica il softmax a ciascuna riga, moltiplica per i valori e otterrai la nuova rappresentazione di quel token.
La maschera è una seconda matrice, della stessa forma, che decide quali di questi punteggi possono esistere. Le voci bloccate sono impostate a meno infinito prima del softmax, in modo da contribuire esattamente con uno zero in seguito.
Prendiamo la riga 4 e applichiamo una normale maschera causale, che blocca tutto ciò che si trova a destra della diagonale:
billing non dà alcun contributo a about. Non poco: zero. Sull'intera matrice questo genera il familiare triangolo.
La maschera stabilisce chi può leggere chi. Non definisce in quale ordine venga eseguito il calcolo. Durante il prefill, tutti e 5 i token sono già presenti, quindi un layer calcola l'intera matrice 5x5 in un colpo solo, applica la maschera e aggiorna insieme tutte e 5 le rappresentazioni. I layer vengono eseguiti in sequenza, le posizioni no. La sequenzialità nella generazione deriva da un altro fattore: quando effettui il campionamento, il token t+1 non esiste finché non hai estratto il token t. Questa è una proprietà del campionamento, non del triangolo. Quindi: fermati prima di campionare, e nulla in un transformer causale risulterà sequenziale. Tutto in questa guida deriva da questa singola frase.
3. Primo tentativo: fermarsi e basta
Se il prefill ha già costruito una rappresentazione completa dello "stato, quindi questa domanda", prendiamola e saltiamo interamente la fase di decode.
Per ora, leggi h attraverso la normale testa LM del modello e guarda solo i logit delle lettere delle opzioni:
Un passaggio, nessun decode, nessun training. Funziona oggi stesso su qualsiasi modello tu distribuisca, ed è la baseline che dovresti misurare prima di costruire qualsiasi cosa.
Cosa ti garantisce. Il ciclo di decode scompare. Problema 2 risolto.
Cosa non ti garantisce. Quelle sono probabilità del token successivo e sono calibrate male. Ecco la misurazione di kev sul backbone Qwen2.5-0.5B non ottimizzato, espressa come accuratezza / ECE:
Task | Base | Instruct |
|---|---|---|
Choice, 4-way (AG News) | 0.813 / 0.069 | 0.787 / 0.160 |
Choice, 3-way (MNLI) | 0.460 / 0.225 | 0.433 / 0.390 |
Noul (BoolQ) | 0.427 / 0.274 | 0.607 / 0.084 |
Score, 5 levels (Yelp) | 0.313 / 0.043 | 0.353 / 0.078 |
Guarda la colonna Instruct: l'instruction tuning ha reso la calibrazione peggiore su tre task su quattro. L'apprendimento con feedback umano (RLHF) acuisce le distribuzioni verso risposte che sembrano sicure di sé, il che è esattamente la pressione sbagliata per un servizio decisionale.
E c'è un problema di costi. Le richieste reali pongono molte domande su un singolo documento. Dieci domande significano dieci chiamate, e ognuna codifica nuovamente lo stesso stato da 20.000 token.
4. Secondo tentativo: una sequenza, maschera standard
La soluzione più ovvia è inserire tutto in un'unica sequenza, in modo che lo stato sia codificato una sola volta.
Eseguilo con la normale maschera causale e leggi l'ultimo stato nascosto dello span di ciascuna domanda. Lo stato viene codificato esattamente una volta. Problema risolto?
No. Guarda cosa permette il triangolo.
Due fallimenti, entrambi gravi.
Contaminazione. La domanda C può leggere le domande A e B. Se chiedi "il cliente è arrabbiato?" prima di "quale coda?", la formulazione legata alla rabbia diventa parte del contesto letto dalla decisione sulla coda. La risposta a una domanda dipende dalle altre domande che hai posto casualmente.
Dipendenza dall'ordine. Inverti A e B nel payload JSON e otterrai risposte diverse, perché occupano posizioni differenti nel triangolo. L'ordine delle chiavi del chiamante, che dovrebbe essere ininfluente, modifica l'output.
L'interfaccia che desideriamo garantisce che le domande siano indipendenti. Il triangolo non offre questo risultato. Quindi, cambiamo il triangolo.
5. Modificare la maschera
Vogliamo che ogni domanda legga lo stato e se stessa, e nient'altro. Si tratta di una piccola modifica: prendiamo il triangolo ed eliminiamo i blocchi in cui una domanda legge l'altra.
Leggilo riga per riga e il design si delinea da sé.
La riga di stato vede solo se stessa. Non dipende mai da alcuna domanda, ed è esattamente il motivo per cui la sua cache KV può essere calcolata una sola volta e riutilizzata. Questa non è un'ottimizzazione inserita a posteriori; è una conseguenza della forma della maschera.
Ogni riga di domanda legge lo stato per intero, oltre ai propri token in modo causale.
Nessuna riga di domanda tocca un'altra domanda. L'isolamento non è imposto da un wrapper o da un'istruzione nel prompt. È imposto dall'aritmetica: quei punteggi sono a meno infinito, quindi contribuiscono per zero.
Nel codice, l'intera modifica si riduce a un singolo predicato.
Quelle aree rimosse non costano nulla. FlexAttention lavora su blocchi (tiles) e salta un blocco che è interamente mascherato, quindi non calcoli mai i punteggi che stai per scartare. La stessa struttura è presente in Hydragen e DeFT, e vLLM e SGLang offrono gran parte di questo risultato attraverso il caching dei prefissi.
Cosa si risparmia effettivamente
È importante essere precisi, poiché il vantaggio principale viene spesso sovrastimato.
Work per layer | Q separate calls | One edited mask |
|---|---|---|
FFN / MoE tokens |
|
|
State attending to itself |
|
|
Questions attending to state |
|
|
L'ultima riga non migliora, e nessuna modifica della maschera può farlo. Ogni domanda deve necessariamente leggere lo stato. Ciò che scompare è tutto il resto e, dato che la rete FFN domina i FLOPs del prefill e S è solitamente molto più grande di n, questo costituisce la maggior parte della spesa. kev rileva che il percorso compattato offre un throughput pari a 2,0x rispetto a chiamate separate.
Questo spiega anche i due limiti esposti dalle API reali: circa 32K per ramo, circa 64K per richiesta, con lo stato conteggiato una sola volta. Uno stato da 23K con 5.000 domande si adatta comodamente. Con chiamate separate, la stessa richiesta supererebbe i 100 milioni di token.
6. Terzo problema: le posizioni
La maschera è corretta, ma il modello si comporta comunque in modo anomalo. Questo perché l'attenzione non usa solo la maschera; con RoPE utilizza anche la distanza tra le posizioni.
Nella sequenza compattata, le domande si trovano a offset differenti:
Di conseguenza, Q_c è più lontana dallo stato rispetto a Q_a, solo a causa della posizione in cui è finita nel payload JSON. Abbiamo eliminato i collegamenti di attenzione tra i rami, ma abbiamo mantenuto l'ordinamento geometrico, e RoPE legge proprio questo ordinamento.
La soluzione è far ripartire gli ID di posizione di ciascun ramo subito dopo lo stato.
Ora ogni domanda occupa lo stesso slot geometrico: "lo stato, poi una domanda". Due token in rami diversi condividono un ID di posizione, il che sarebbe un bug in una sequenza normale mentre qui è innocuo per un'unica ragione: non possono mai prestare attenzione l'uno all'altro. La modifica della maschera è ciò che rende sicura la modifica delle posizioni.
Questo è lo stesso trucco dell'attenzione ad albero (tree attention) nel decoding speculativo: l'ordine delle domande smette di essere importante.
7. Quarto problema: sliding windows
Questo vale solo se il tuo backbone ne è provvisto. Qwen2.5, utilizzato da kev, adotta un'attenzione completa ovunque, quindi kev non incontra questo limite. Gemma 4 sì.
25 dei 30 layer di Gemma 4 utilizzano una sliding window da 1.024 token. Solo 5 utilizzano l'attenzione completa. Questa è una terza maschera, combinata in AND con la nostra:
La maggior parte dello stato è invisibile a quel livello. Viene trasmesso attraverso i 5 layer globali e attraverso qualsiasi elemento i layer locali abbiano già integrato nei token di stato vicini al confine del ramo. Questo comportamento non è errato, è lo stesso vincolo con cui opera il modello di base per qualsiasi prompt lungo. Tuttavia, due aspetti richiedono attenzione.
Calcola la finestra sulle posizioni reimpostate, non sull'indice compattato. Altrimenti l'ultimo ramo del payload otterrà una finestra effettiva diversa rispetto al primo, reinserendo silenziosamente la stessa dipendenza dall'ordinamento rimossa nella parte 6.
Poi, effettua una misurazione. Posiziona un dato specifico a varie profondità nello stato, poni una domanda che ne richieda il recupero e traccia la capacità di recall in relazione alla lunghezza dello stato. Se cala drasticamente oltre poche migliaia di token, puoi promuovere più layer all'attenzione completa e ri-ottimizzarli tramite fine-tuning, oppure limitare la lunghezza dello stato a livello di prodotto. Nessuna implementazione ha testato questo aspetto; è una mia deduzione basata sull'architettura.
8. Ora l'altra estremità: sostituire la testa LM
La parte di input è completata. Quella di output è ancora un modello linguistico.
L'ultimo stato nascosto viene attualmente proiettato in 262.144 logit di vocabolario, dei quali ne utilizziamo tre. Proiettalo invece in uno spazio più piccolo.
Esistono due modi per costruire il readout.
Una testa a slot (slot head) mappa su slot posizionali: l'output 0 indica "la prima opzione nell'elenco", qualunque essa sia. Il testo del ramo fornisce il significato di ciascun slot, in modo che una sola testa possa servire il set di etichette di qualsiasi cliente senza bisogno di ri-addestramento. Una testa a 256 slot spiegherebbe il limite di 255 opzioni delle API, che equivale a 2^8 meno uno.
Una testa a puntatore (pointer head) valuta ogni opzione rispetto alla posizione della decisione:
Le sonde esterne di Archer non consentono di distinguere i due approcci. kev risolve la questione pratica implementando il puntatore, e il motivo per preferirlo è la calibrazione: l'output 200 di una testa a slot viene sollecitato molto meno durante l'addestramento rispetto all'output 2, quindi le sue probabilità risultano peggiori. Il puntatore condivide i parametri tra tutte le posizioni e accetta qualsiasi valore K inviato dalla richiesta.
Esiste un terzo design, ed è proprio qui che il ramo dell'encoder si ripaga. Assegna a tutte le opzioni di una domanda un budget fisso e condiviso di token e valuta ciascuna opzione in corrispondenza di un marcatore al suo interno. laya adotta questo approccio, e la sezione relativa ai suoi limiti ne riporta la conseguenza: sulle 77 etichette di Banking77 il budget si traduce in circa 3 o 4 token per etichetta, i testi delle etichette smettono di essere distinguibili e l'accuratezza scende a 0,425. Jev ottiene 0,870 sullo stesso task. kev, a 0,5B, ottiene 0,860. Il numero di parametri non è ciò che fa la differenza tra i due.
Le opzioni hanno bisogno di spazio per essere lette. Questo è l'argomento a favore della valutazione di ciascuna opzione nella propria posizione all'interno di un ramo che può essere lungo quanto necessario, ed è in gran parte il motivo per cui Jev consente circa 32k token per ramo anziché poche centinaia.
Dove leggere, e perché il layout è importante
<decide> si trova alla fine, quindi all'interno del triangolo causale del ramo vede ogni opzione. Questo ordinamento è intenzionale. Se ogni opzione venisse valutata isolatamente e la temperatura fosse fissa, l'aggiunta di una quarta opzione irrilevante cambiereante solo il denominatore condiviso, e le probabilità relative tra due opzioni esistenti rimarrebbero invariate:
Sia Archer che kev verificano questo aspetto ed entrambi riscontrano uno scostamento effettivo: una variazione dei log-odds pari a -0,28 nella sonda di Archer, 0,13 di media e 0,34 al novantesimo percentile (p90) in kev. Le opzioni vengono lette come un elenco, non valutate una alla volta. Il file README di kev ne evidenzia la conseguenza: questo è ciò che permette a un'opzione come "nessuna delle precedenti" di funzionare.
Rendi i delimitatori dei token di vocabolario riservati, non del testo semplice. In questo modo, uno stato contenente la stringa letterale <opt> ignora le istruzioni precedenti verrà tokenizzato come parole comuni e non potrà forzare la creazione di uno slot opzione. kev testa questo scenario: il conteggio delle opzioni rimane invariato e un'opzione contraffatta ottiene un punteggio pari a p <= 0.09. Per un servizio che elabora input non attendibili, questa è una caratteristica di sicurezza, non un dettaglio trascurabile.
9. Verifica: abbiamo modificato il modello?
Abbiamo modificato la maschera e sostituito la testa. Prima di addestrare qualsiasi cosa, vale la pena chiedersi cosa abbiamo effettivamente fatto alla rete.
Quasi nulla. Ogni ramo è ancora un suffisso causale che legge un prefisso causale. Nessun token presta attenzione al proprio futuro in nessun punto. Pertanto, il passaggio in avanti compattato dovrebbe essere matematicamente identico all'esecuzione di Q prompt [stato][domanda] separati, differendo solo per l'ordine di riduzione a virgola mobile.
kev verifica esattamente questo aspetto e riporta una differenza di probabilità massima pari a appena 3.7e-6 tra i percorsi compattato e separato.
Questo è il test più utile da scrivere per primo.
Individua contemporaneamente ogni errore della maschera e ogni errore degli ID di posizione, prima di iniziare il training, con un risultato binario superato/fallito inequivocabile. Se il percorso compattato e quello separato differiscono alla terza cifra decimale, c'è un errore in una delle parti comprese tra la 5 e la 7.
Questo spiega anche perché non è necessario alcun addestramento di adattamento per la maschera. Al contrario, in DiffusionGemma, che converte l'attenzione causale in attenzione bidirezionale, i token iniziano a prestare attenzione al proprio futuro, una dinamica che il modello di base non ha mai incontrato e che costituisce un reale cambiamento di distribuzione che richiede un vero e proprio ri-addestramento. La nostra modifica si limita a rimuovere collegamenti.
L'isolamento si mantiene anche a livello di comportamento. kev riproduce la sonda a codice segreto di Archer: un dato inserito in una domanda correlata viene recuperato con p = 0.03, un valore identico a quando non è presente affatto, rispetto a p = 0.99 quando lo stesso dato si trova nello stato condiviso.
10. Infine: le probabilità non sono ancora probabilità
Finora tutto ciò che abbiamo fatto ha sistemato la forma del calcolo. La testa di readout non è addestrata e il backbone non ha mai visto questo formato. È il momento dell'ultimo passaggio.
La calibrazione è una proprietà delle previsioni su più casi, mai di una singola previsione.
Prendi tutti i casi a cui hai assegnato una probabilità di 0,8. La calibrazione verifica se circa l'80% di essi si è effettivamente verificato in quel modo. Non puoi stabilire se una singola previsione sia calibrata osservando esclusivamente come si è concluso quel singolo caso.
Fase A: solo la testa (head-only)
Blocca il backbone, addestra solo la testa di readout. Trattandosi di una matrice d × K, questo processo non costa quasi nulla e ti fornisce un sistema end-to-end funzionante in un'ora. Il suo vero valore risiede nel validare l'infrastruttura prima di investire budget. Offrirà prestazioni inferiori, poiché le rappresentazioni del backbone sono state modificate per la predizione del token successivo.
Fase B: insegnare il formato al backbone
Sbloccalo tramite LoRA. kev utilizza r=16 sul backbone con la testa addestrata da zero, e l'intero artefatto pesa 38 MB.
Ciò che stai insegnando non è la conoscenza, che è già presente nel checkpoint, ma il formato: cosa significa <decide>, come leggere un elenco di opzioni e che non è previsto l'arrivo di altro testo.
La perdita (loss) deve essere una regola di punteggio appropriata (proper scoring rule), ovvero una regola la cui perdita attesa viene minimizzata riportando la reale distribuzione condizionale. L'entropia incrociata (cross-entropy) è la scelta più ovvia ed è quella impiegata da kev.
TypeSafe definisce il proprio obiettivo RLCD (Reinforcement Learning for Calibrated Decisions) e laya pubblica un'implementazione concreta con questo nome: la policy riporta una distribuzione, l'esplorazione aggiunge rumore gaussiano a media zero ai logit, la ricompensa è una proper scoring rule e gli aggiornamenti si basano su REINFORCE con una baseline di media di gruppo. Vale la pena definire con precisione cosa apporti l'approccio basato su RL, poiché l'acronimo sembra più esotico del meccanismo reale. Per una predizione a singolo step a fronte di un'etichetta nota, l'RL basato su una proper scoring rule e l'entropia incrociata supervisionata ottimizzano lo stesso obiettivo. La log loss è la regola di punteggio logaritmica; un gradiente di policy in quel contesto rappresenta una stima a varianza più elevata di un gradiente che puoi calcolare in modo esatto. Il framework di RL si rivela utile solo laddove non sia possibile differenziare fino all'etichetta: ricompense ritardate o non differenziabili, o l'assegnazione del credito su più turni, che è lo scopo del TD di laya sui prefissi di conversazione.
Evita il label smoothing.
Si tratta di una proper scoring rule volutamente alterata: spinge le predizioni verso una distribuzione a priori fissa, indipendentemente da ciò che indicano i dati. Va bene quando ti interessa solo l'argmax. È del tutto errata quando il prodotto stesso è costituito dalla probabilità.
Dati e i due termini aggiuntivi
kev effettua l'addestramento su sei dataset pubblici riadattati nel formato di richiesta target: Banking77 (Choice a 77 opzioni), AG News (Choice più sì/no), MNLI (a 3 opzioni), BoolQ (Noul), SST-5 e Yelp (Score a 5 livelli). 9.000 record, 13.500 domande, due epoche, circa 1h45m su un chip Apple M5.
Vale la pena adottare due termini di loss:
--perm_kl, una KL simmetrica tra le predizioni ottenute con due ordini di opzioni casuali. Questo interviene sulla sensibilità all'ordine in fase di training piuttosto che in fase di inferenza.--ord_w, un termine ordinale che penalizza|E[level] - y|per le domande di tipo Score. L'entropia incrociata pura tratta gli errori "fuori di un livello" e "fuori di tre livelli" come ugualmente gravi, il che è evidentemente un presupposto errato per una scala ordinata. Se stai sviluppando questo aspetto da zero, ti conviene utilizzare il ranked probability score, impiegato anche da laya: si tratta della proper scoring rule per i risultati ordinali, rappresentando la versione rigorosa della stessa correzione anziché una penalità aggiunta artificialmente.
Arricchisci ogni esempio con un ordine di opzioni mescolato, un'opzione irrilevante aggiunta alla fine e un numero variabile di domande correlate. Queste tre tecniche di data augmentation mirano esattamente ai tre comportamenti strutturati nelle parti da 5 a 8.
Un unico renderer, condiviso tra training e serving.
kev indirizza i dati di addestramento e le richieste live attraverso lo stesso codice, in modo che il modello non incontri mai in fase di inferenza un formato che non ha visto durante l'addestramento. Sembra un concetto troppo ovvio da specificare, eppure è l'errore più comune, poiché la pipeline di training e quella di serving vengono solitamente scritte a mesi di distanza da persone diverse.
Fase C: calibrazione
Un obiettivo corretto definisce lo scenario ottimale, ma non garantisce il comportamento del modello addestrato. Dati finiti, capacità limitata e variazioni di distribuzione lasciano sempre la calibrazione reale imperfetta.
Il temperature scaling si rivela fondamentale, e l'effetto non è trascurabile. Un singolo scalare calcolato su dati di validazione (held-out) e applicato ai logit. kev riporta un ECE sui dati di validazione pari a 0,065, che scende a 0,031 dopo l'applicazione di quel singolo parametro. laya, che presenta un comportamento nativo di eccessiva sicurezza (over-confident), vede l'ECE medio scendere da 0,466 a 0,081.
Calcola una temperatura per ciascuna combinazione di (tipo di domanda, numero di opzioni), non semplicemente per tipo. L'acutezza di una distribuzione dipende da K, quindi una scelta a 3 opzioni e una a 77 opzioni non hanno motivo di condividere lo stesso scalare. Questa è un'ottimizzazione introdotta da laya che merita di essere replicata.
Target morbidi (soft targets), laddove disponibili. Cinque annotatori che si dividono sul 3-2 indicano che il target è [0.6, 0.4], non un valore binario (one-hot). Etichette rigide (hard labels) su casi realmente ambigui addestrano il modello a un'eccessiva sicurezza, e i casi ambigui sono proprio quelli in cui la probabilità è fondamentale. Le etichette basate sui risultati effettivi ("questa escalation era davvero necessaria?") superano quelle fornite dagli annotatori. Nessuna delle due implementazioni adotta questo approccio; entrambe utilizzano dataset pubblici con etichette rigide, ed è il passo successivo più ovvio per un deployment reale.
Un campo che non dovresti addestrare. Se mostri una confidenza scalare accanto alla distribuzione, calcolala nel codice dell'applicazione. L'SDK di TypeSafe fa proprio questo per K > 1:
Misura quanto la risposta principale superi una distribuzione uniforme. È pura aritmetica, non una seconda stima appresa sulla correttezza della risposta. Mantenere questi due elementi separati previene l'errore concettuale più comune in questo ambito: una distribuzione concentrata può comunque essere errata con la massima sicurezza.
11. Funziona?
kev, con parametri da 0,5B, su 1.350 domande di validazione (held-out). Accuratezza / ECE su 10 raggruppamenti (bins).
Task | Base backbone | After parts 5 to 10 |
|---|---|---|
Choice, 4-way (AG News) | 0.813 / 0.069 | 0.940 / 0.028 |
Choice, 3-way (MNLI) | 0.460 / 0.225 | 0.747 / 0.100 |
Choice, 77-way (Banking77) | not runnable | 0.860 / 0.057 |
Noul (BoolQ) | 0.427 / 0.274 | 0.753 / 0.136 |
Score, 5 levels (Yelp) | 0.313 / 0.043 | 0.553 / 0.118 |
All | 0.799 / 0.065 |
Nota la riga a 77 opzioni. Leggere le lettere delle opzioni da una distribuzione del token successivo non è scalabile a 77 opzioni; una testa a puntatore ci riesce, senza dover modificare nulla.
La suite di valutazione (eval suite)
Ciascun test riportato di seguito si ricollega a una sezione di questa guida, il che li rende preziosi da eseguire nei sistemi di Continuous Integration (CI): un'eventuale regressione indica esattamente quale modifica ha causato l'errore.
Test | Guards | kev-0.5b |
|---|---|---|
Packed vs separate | Parts 5 and 6 |
|
Isolation probe | Part 5 |
|
Permutation | Parts 6 and 8 | 7.4% argmax flips |
Choice-set (IIA) | Part 8 | 0.13 mean, 0.34 p90 |
Boundary forgery | Part 8 | forged |
Binned ECE | Part 10 | 0.065, 0.031 tempered |
Due note per l'interpretazione di questa tabella. Il dato del 7,4% sulle permutazioni rappresenta il valore non regolarizzato: la versione rilasciata di kev-0.5b precede l'introduzione di --perm_kl ed è addestrata con questo parametro impostato a 0. Inoltre, è importante riportare il conteggio dei bin insieme all'ECE, poiché la concordanza complessiva può nascondere un'eccessiva sicurezza in un'area compensata da una scarsa sicurezza in un'altra.
La gestione economica delle soglie (threshold economics) è il valore di reale interesse per il business. Se un'errata escalation costa 1 e un caso urgente non rilevato costa 9, la regola impostata sarà p(urgent) > 0.1. Modula la soglia in base alla tua reale matrice dei costi.
E lo scenario di errore da evitare nel design è quello in cui il modello è sicuro ma sbaglia. Il checkpoint in inglese di laya registra un'accuratezza pari a 0,000 con una confidenza di 0,952 in lingua khmer. Certezza massima, errore massimo, per cui nessuna soglia di confidenza può intercettarlo e nessun livello di calibrazione sull'inglese ti avrebbe avvisato. La loro soluzione consiste nel rilevare lo script in meno di 0,5 ms tramite Python standard prima del passaggio in avanti, un approccio che si traduce in una regola d'oro: il rilevamento di dati fuori distribuzione (out-of-distribution) deve avvenire all'esterno del modello della cui confidenza non ti fidi. Un modello calibrato lo è solo rispetto alla distribuzione su cui è stato calibrato, e non ha alcun modo per avvisarti quando ne è uscito.
kev descrive chiaramente il limite più rilevante, valido per chiunque sviluppi soluzioni in questo modo: l'ECE è calcolato all'interno della distribuzione (in-distribution). I subset di test provengono dagli stessi dataset di addestramento. La calibrazione su Banking77 non garantisce nulla sulla calibrazione dei tuoi ticket. Una reale calibrazione necessita di dati etichettati in base ai risultati concreti estratti dal workflow in cui stai effettuando il deployment.
12. A cosa rinunci
Nessuna catena di pensiero (chain of thought). Tutto si sviluppa in un unico prefill. I processi di ragionamento complessi a più passaggi rappresentano il punto debole di questo design, ambito in cui un modello autoregressivo con uno spazio di lavoro (scratchpad) conserva un netto vantaggio.
Nessuna dipendenza tra domande diverse. Se B necessita realmente della risposta di A, l'isolamento non apporta alcun beneficio. Uniscile in un'unica domanda sullo spazio dei prodotti, oppure prevedi un secondo ciclo di elaborazione. Condividere il contesto non elimina la struttura logica del tuo workflow.
Nessun output a testo libero (open-ended). Il set di opzioni deve essere predefinito e noto al momento della richiesta.
Ogni richiesta comporta lo stesso costo di elaborazione. Non esiste una via rapida ed economica per i casi più semplici.
La conoscenza è limitata dal backbone, e questo rappresenta il vero limite invalicabile. kev lo riconosce apertamente: a 0,5B sceglie
return_policyladdove Jev individua correttamentereturn_status. Il comportamento di laya è ancora più evidente, con performance inferiori alla baseline zero-shot sul proprio benchmark. Nessuna di queste modifiche aggiunge conoscenza. Cambiano solo il modo in cui viene letta, motivo per cui la scelta del fork iniziale prima della parte 2 costituisce la decisione più critica di tutta la guida.
Il vantaggio è che il grafo computazionale corrisponde finalmente al compito da svolgere. Un servizio decisionale esamina le prove, confronta i risultati ammessi e restituisce il livello di incertezza. Un transformer può eseguire tutte e tre le operazioni senza dover prima convertire ogni decisione in una frase.
Domande frequenti
Cos'è un modello decisionale simile a Jev?
È un modello che risponde a diverse domande di classificazione su un singolo documento in un unico passaggio in avanti. Invece di generare testo, restituisce una distribuzione di probabilità su un insieme chiuso di opzioni. Il design segue la ricostruzione di Archer Hume del Jev di TypeSafe, implementata in kev su Qwen2.5-0.5B.
Perché non chiedere semplicemente a un LLM di generare un punteggio di confidenza?
Questo perché quel punteggio è testo generato. La probabilità che il modello scriva "0.91" non coincide con la probabilità che la sua risposta sia corretta, e nulla nella fase di addestramento collega le due cose. Nei test di kev, l'instruction tuning ha peggiorato la calibrazione su tre task su quattro.
In che modo l'editing dell'attention mask è d'aiuto?
Ogni domanda può leggere il documento condiviso e i propri token, ma mai un'altra domanda. Il documento viene codificato una sola volta e riutilizzato, e le risposte non cambiano più in base alle altre domande che poni o al loro ordine. Questo garantisce un throughput doppio rispetto alle chiamate separate.
Modificare la maschera comporta il riaddestramento del modello?
Non per la maschera stessa. La modifica rimuove solo i bordi di attenzione, in modo che il passaggio pacchettizzato corrisponda quasi esattamente a prompt separati (una differenza massima di 3.7e-6 in kev). L'addestramento è ancora necessario per la nuova testa di lettura e il formato di input: kev utilizza LoRA con entropia incrociata, seguita dal ridimensionamento della temperatura.
Cosa significa calibrazione in questo contesto?
Un modello è calibrato se, su tutti i casi a cui assegna un punteggio di 0,8, circa l'80% si rivela corretto. Non puoi giudicarlo da una singola previsione. L'errore di calibrazione held-out di kev scende da 0,065 a 0,031 dopo il temperature scaling, anche se solo su dati simili ai suoi set di addestramento.
Un encoder bidirezionale come BERT non è l'opzione più semplice?
Salta le modifiche alle maschere, ma i migliori encoder si attestano intorno ai 400 milioni di parametri e non aggiungono conoscenza. laya, basato su ModernBERT-large, ottiene un punteggio inferiore alla baseline della classe di maggioranza in modalità zero-shot e raggiunge solo lo 0,766 dopo il fine-tuning sui dati di addestramento del benchmark stesso. Gli encoder si adattano a workflow noti con dati etichettati. Per gli schemi che il modello non ha mai visto, un decoder è il punto di partenza migliore


Resta aggiornato su ciò che stiamo imparando, costruendo e osservando mentre i team enterprise distribuiscono e misurano gli agenti AI in produzione.



