Ingegneria
Un valore predefinito invertito fra due file
Quando la configurazione di un modello non nomina tie_word_embeddings, phi3 assume falso e llama assume vero. Copiane uno e avrai un modello giusto su una famiglia e silenziosamente sbagliato sull'altra.
Aggiornato 2026-09-12Misurato 2026-09-06
Ecco un difetto impossibile da trovare provando un modello, e banale da trovare provandone due.
In mlx-lm, quando il config.json di un modello non nomina tie_word_embeddings, il valore
predefinito cambia a seconda del file dell'architettura. phi3.py assume false. llama.py
assume true. Sono corretti entrambi per i modelli per cui sono stati scritti, e nessuno dei due
è una regola generale.
Quindi se stai portando un'architettura e copi il default dal file che hai letto per primo, avrai ragione su quella famiglia e torto sull'altra. E «torto» qui non significa un'eccezione.
Cosa controlla davvero il campo
Un modello di linguaggio ha una tabella di embedding che all'ingresso trasforma gli identificatori dei token in vettori, e una proiezione che all'uscita trasforma i vettori in punteggi sul vocabolario. Le due hanno la stessa forma, trasposta.
Legate significa che sono gli stessi pesi: la proiezione d'uscita è la trasposta
dell'embedding d'ingresso, e il file contiene una matrice sola. Slegate significa che sono
separate, apprese indipendentemente, e il file ne contiene due — la seconda di solito chiamata
lm_head.weight.
Legarle risparmia molta memoria su un modello piccolo, dove la matrice del vocabolario può essere una fetta importante dei parametri. Slegarle dà al modello più capacità. Famiglie diverse hanno scelto diversamente, ed è per questo che il campo esiste.
Perché sbagliarlo è silenzioso
Se assumi legate mentre il modello le ha slegate, usi l'embedding d'ingresso come proiezione d'uscita. La matrice ha la forma giusta. La moltiplicazione riesce. Ottieni dei logit.
Sono logit calcolati con la matrice sbagliata, quindi la distribuzione d'uscita del modello non è quella per cui è stato addestrato — ma non è nemmeno rumore, perché in un modello addestrato con le due separate le due restano imparentate. Il testo resta scorrevole. Peggiora e basta, in un modo che si legge come «questa quantizzazione perde un po'» oppure «questo modello non è granché».
Il verso opposto è più rumoroso: assumere slegate quando i pesi sono legati significa cercare
lm_head.weight e non trovarlo, il che almeno fallisce.
La correzione è smettere di indovinare
La risposta affidabile non è un default migliore. È guardare i pesi.
Se lm_head.weight c'è nei safetensors, gli embedding sono slegati — perché un modello slegato
quella matrice deve spedirla, e uno legato non ha nessun motivo di farlo. Se non c'è, sono legati.
Quel controllo non è un'euristica. È il fatto stesso: la presenza del tensore è ciò che «slegati» significa a livello di file. Un campo della configurazione è un'affermazione sul file, e dove i due non concordano vince il file.
Quindi l'ordine è: leggi il campo se c'è, e se non c'è cerca il tensore. Mai ricadere su una costante.
Controllare un modello in trenta secondi
Visto che la risposta sta nei pesi, ecco la ricetta pratica, che non richiede di scaricare niente.
Apri il modello su Hugging Face e guarda l'elenco dei file. Se vedi lm_head.weight in un
indice di safetensors, o un file il cui nome lo suggerisce, gli embedding sono slegati. Su un
modello a shard, il file di indice — model.safetensors.index.json — ha una weight_map che
elenca ogni nome di tensore, ed è un normale file JSON che puoi leggere nel browser.
Poi apri config.json e cerca tie_word_embeddings. Tre esiti:
- Dice
trueelm_head.weightnon c'è — coerente, legati, niente su cui pensare. - Dice
falseelm_head.weightc'è — coerente, slegati, idem. - Non dice niente — è il caso comune, ed è quello su cui le due librerie non concordano. Decidono i pesi.
La quarta combinazione, in cui configurazione e pesi si contraddicono, è rara e conviene trattarla come un repository rotto invece che come un enigma da risolvere: quasi sempre è il segno che qualcuno ha convertito un modello e si è portato dietro la configurazione originale senza rileggerla, e in quel caso è probabile che non sia l'unico campo rimasto indietro.
Lo schema generale
Questa è un'istanza di qualcosa che vale la pena riconoscere, perché si ripete in ogni runtime di modelli.
Le implementazioni di riferimento sono scritte per famiglia, e i loro default sono sapere
locale. Un valore dentro llama.py non è un'affermazione sui modelli di linguaggio: è
un'affermazione su llama. Leggere un file e generalizzare è il modo in cui si eredita un
presupposto che non era mai stato pensato per viaggiare.
La stessa forma ricompare nella stessa base di codice. I bias dell'attenzione, per esempio: qwen2
li ha sulle proiezioni di query, chiave e valore e non li dichiara da nessuna parte, perché in
mlx-lm stanno nell'architettura invece che nella configurazione. Llama li rende opzionali con
attention_bias. Una porta che legge solo la configurazione sbaglia qwen2; una che aggiunge
sempre i bias sbaglia llama. Guardare se il tensore esiste risponde a entrambe.
Che è la stessa regola di prima, e si generalizza:
Quando la configurazione e i pesi non concordano su cosa sia un modello, il modello sono i pesi.
Come accorgersene prima di spedire
L'unico motivo per cui questa cosa è stata trovata invece che spedita è che la correttezza qui si verifica contro un riferimento, non leggendo l'uscita.
mlx-lm genera oracoli: per un dato modello e prompt, la sequenza esatta di token e i logit
grezzi. Una porta deve riprodurli. Una proiezione d'uscita sbagliata cambia i logit subito e in
modo visibile — in numeri, non in sensazioni — e il test fallisce al primo token.
Se stai portando architetture e non ce l'hai, vale la pena costruirlo prima della seconda famiglia invece che dopo la decima. L'alternativa è leggere del testo generato e farsi un'impressione, e un'impressione non sa distinguere «matrice sbagliata» da «modello piccolo».
Un'avvertenza sul confronto dei logit, visto che è il lavoro dello stesso pomeriggio: la tolleranza non può scendere sotto un passo del tipo dei logit. Qwen3 li produce in bfloat16, dove a 16 un passo vale 0,125 — quindi pretendere 0,05 è pretendere l'uguaglianza bit a bit chiamandola tolleranza.
Cosa costa quando è giusto
Vale un paragrafo, perché il campo non è gratis in nessuno dei due versi e lo scambio è reale.
Su un modello piccolo la matrice del vocabolario è una fetta grande di tutto. Un modello con un vocabolario da 128.000 token e una dimensione nascosta di 2.048 porta circa 262 milioni di parametri in quella sola matrice — che su un modello da un miliardo di parametri è circa un quarto. Legarle dimezza quel costo, e su un modello così piccolo la memoria risparmiata è la differenza fra starci comodi e non starci.
Su un modello grande la stessa matrice è un arrotondamento, e la capacità in più che viene dallo slegarle vale più della memoria. Ecco perché la scelta è correlata alla dimensione, ed ecco perché un default giusto per una famiglia è sbagliato per l'altra: le due librerie non erano sbadate. Stavano ognuna descrivendo i propri modelli.
La versione corta
tie_word_embeddingsvalefalsein phi3 etruein llama, nella stessa libreria.- Sbagliarlo produce testo scorrevole e peggiore, non un errore.
- Non scegliere un default: guarda se
lm_head.weightè fra i pesi. - Stessa regola per i bias dell'attenzione, e per qualunque cosa la configurazione possa omettere.
Un'ultima cosa, visto che è la domanda che questa storia solleva sempre: no, non lo si capisce dal nome del modello, dal numero di parametri, né dalla famiglia. Due modelli dello stesso pubblicatore, usciti a un mese di distanza, possono differire. L'elenco dei file è l'unico posto in cui la risposta vive.
Verificato contro mlx-lm 0.31.3 il 6 settembre 2026.