Vai al contenuto principale

Tutorial Tinker: fine-tuning degli LLM con Thinking Machines Lab

Una guida pratica per implementare LoRA con Tinker AI e addestrare Qwen3-8B su dati finanziari Chain-of-Thought (CoT), ottimizzando la generalizzazione.
Aggiornato 29 set 2026  · 15 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

Il fine-tuning dei large language model di solito significa gestire infrastrutture GPU distribuite, affrontare guasti di cluster e fare debug di script di training complessi. Tinker nasce per risolvere tutto questo.

Rilasciato dal Thinking Machines Lab di Mira Murati il 1 ottobre 2025, Tinker è un'API di training che gestisce tutta la complessità dell'infrastruttura lasciandoti il pieno controllo su algoritmi e dati. Permette di scrivere semplici script Python con quattro funzioni principali, e Tinker li esegue su GPU distribuite per un'ampia gamma di modelli open source come Llama 70B o Qwen 235B.

La piattaforma utilizza il fine-tuning con Low-Rank Adaptation (LoRA) per ridurre i costi e supporta tutto, dall'apprendimento supervisionato al reinforcement learning. Team di ricerca a Princeton, Stanford e Berkeley la usano già nei loro lavori.

In questo tutorial, ti guiderò nell'installazione di Tinker, nella comprensione della sua API e nel fine-tuning di un modello completo di Q&A finanziario usando il modello base Qwen3-8B. Se vuoi approfondire il fine-tuning con un po' di pratica, ti consiglio questo corso su Fine-Tuning con Llama 3.

Che cos'è Tinker?

Thinking Machines Lab ha creato Tinker per chi vuole personalizzare il modo in cui i modelli apprendono senza dover diventare esperti di infrastrutture.

Chi usa Tinker?

La piattaforma si rivolge a tre gruppi: 

  • Ricercatori che esplorano nuovi metodi di training
  • Sviluppatori che costruiscono prodotti AI con comportamenti personalizzati
  • Builder che vogliono risultati di qualità produttiva senza risorse enterprise. 

Tutti e tre condividono un'esigenza: sanno cosa vogliono che il modello faccia, ma le interfacce standard di fine-tuning non offrono abbastanza controllo.

Per esempio, i team accademici potrebbero usarla per testare algoritmi innovativi. Un laboratorio di chimica potrebbe voler addestrare modelli su schemi di ragionamento specifici del dominio che non rientrano nei tipici template di instruction-tuning. Una startup che crea un bot consulente finanziario potrebbe usare il modello per seguire formati di output e catene di ragionamento specifiche. 

Tutti questi casi d'uso hanno in comune la necessità di modificare il processo di training, non solo di cambiare i dataset.

Cosa rende Tinker diverso?

La maggior parte delle piattaforme ottimizza per una di due cose: facilità d'uso o flessibilità. L'approccio di Tinker è che le due non devono essere in conflitto. La piattaforma ti dà accesso di basso livello al training tramite quattro operazioni core, ma gestisce tutto il resto in automatico:

  • Controllo del training loop: Scrivi funzioni di loss personalizzate, strategie di accumulo dei gradienti o schemi di campionamento.
  • Aggiornamenti del modello: Specifica esattamente come devono cambiare i pesi durante l'ottimizzazione.
  • Valutazione: Genera output o calcola probabilità in qualsiasi punto del training.
  • Gestione dello stato: Salva e riprendi l'addestramento con pieno controllo su ciò che viene mantenuto.

Tinker espone queste funzionalità tramite quattro primitive API che imparerai nella sezione pratica.

Questo funziona perché la maggior parte della complessità del training deriva dall'infrastruttura, non dagli algoritmi. Eseguire un training loop personalizzato sul tuo laptop è semplice. Eseguirlo su 100 GPU con ripristino dai guasti e schedulazione delle risorse è difficile. Tinker si occupa della seconda parte, così tu puoi concentrarti sulla prima.

Dove si colloca Tinker

L'ecosistema degli strumenti AI si divide grossolanamente in tre livelli: provider di compute cloud che forniscono GPU raw, piattaforme gestite che eseguono workflow predefiniti e framework che aiutano a costruire sistemi di training da zero. Tinker si colloca tra le piattaforme gestite e i framework. Hai più controllo rispetto a piattaforme come Hugging Face AutoTrain, ma meno lavoro d'infrastruttura rispetto a configurare un cluster personalizzato su Google Cloud Platform (GCP).

Prezzi e crediti di Tinker

Tinker usa un pricing trasparente per token che varia in base alla dimensione del modello e al tipo di operazione. Addestrare Qwen3-8B costa $0,40 per milione di token. La buona notizia: i nuovi utenti ricevono $150 in crediti quando vengono sbloccati dalla waitlist, più che sufficienti per diversi cicli di training sperimentali come quello di questo tutorial.

Prezzi Tinker

Fonte: Thinking Machines Lab

Esempio Tinker: fine-tuning di Qwen3-8B per il Q&A finanziario

In questa sezione, ti guiderò nella creazione di un workflow di fine-tuning completo usando l'API di Tinker. Faremo il fine-tuning di Qwen3-8B su dati di question-answering finanziario usando LoRA, imparando tutte e quattro le primitive API principali e vedendo in azione le best practice di produzione.

Per una panoramica più dettagliata del modello, dai un'occhiata a questo articolo su Qwen3.

Nota importante: Tinker è attualmente in beta con accesso su waitlist. Se hai accesso, puoi seguire ed eseguire il codice. Se sei in lista d'attesa, puoi comunque leggere per capire quanto sia lineare il processo di fine-tuning con Tinker. Quando avrai accesso, sarai pronto a partire subito.

Risultati del training e scelta del checkpoint

Vediamo prima i risultati del training. Capire cosa è successo ti aiuta a riconoscere questi pattern quando esegui i tuoi job di fine-tuning.

Abbiamo fatto il fine-tuning di Qwen3-8B sul dataset FinCoT, un dataset di ragionamento finanziario chain-of-thought, usando LoRA con rank 32. Dopo aver filtrato le sequenze non più lunghe di 10.000 token, sono rimasti 7.244 esempi di training e 500 esempi per la validazione. 

Il training è durato 904 iterazioni su quattro epoche, impiegando circa tre ore per completarsi, mentre Tinker ha gestito tutta la coordinazione GPU distribuita. 

Un'epoca è un passaggio completo su tutto il dataset di training. Poiché c'erano 226 batch per epoca (7.244 esempi divisi per un batch size di 32), le quattro epoche hanno totalizzato 904 iterazioni.

Analisi dei risultati di training

Il grafico seguente traccia tre metriche: training loss (blu), validation loss (rosso) e learning rate (verde).

Avanzamento training Tinker

La fase iniziale (iterazioni 0-400) è sana: sia la training loss che la validation loss scendono rapidamente insieme, passando da oltre 1,4 a sotto 0,8 e restando a circa 0,1 di distanza. 

Questo periodo dimostra che il modello sta imparando pattern generalizzabili nei dati. Il learning rate (linea verde) sale gradualmente durante le prime 200 iterazioni, mentre il warmup stabilizza il training. 

Intorno alle iterazioni 400-600, le cose cambiano. La training loss continua a scendere, ma la validation loss si appiattisce intorno a 0,75-0,8, allargando il gap a 0,13 mentre il modello inizia a overfittare. Tra le iterazioni 600-900, la divergenza diventa evidente: la training loss precipita a 0,39 mentre la validation loss sale a 0,8, segno che il modello è passato dall'apprendere schemi di ragionamento al memorizzare esempi. 

Questo pattern è comune nel fine-tuning di modelli di ragionamento. Se ti interessa come si sviluppano le capacità di ragionamento durante il training, dai un'occhiata a questo tutorial sul fine-tuning di DeepSeek R1, che esplora dinamiche simili.

Il modello salvato al checkpoint-400 offre la migliore generalizzazione, con una training loss di 0,68, una validation loss di 0,73 e un gap ridotto di appena 0,05. Pur avendo una training loss più alta rispetto al checkpoint finale, gestisce meglio le domande nuove. La loss più bassa in training raramente produce il modello migliore: ecco perché le metriche di validazione contano.

Verificheremo più avanti confrontando il modello al checkpoint-400 con il modello base usando un LLM judge. Per ora, vediamo come costruire questo workflow di fine-tuning passo dopo passo.

Passo 1: Installa Tinker e prepara l'ambiente

Nota: I passaggi seguenti scompongono uno script di circa 300 righe. Poiché le spiegazioni sono intrecciate al codice, ti consigliamo di aprire lo script completo in una nuova scheda per avere il quadro completo mentre segui.

Ti serviranno Python 3.11 o successivo e una connessione Internet stabile. Poiché Tinker gestisce il training su GPU nei propri server, una buona connessione è più importante di un computer potente.

Inizia accedendo alla console Tinker e creando una chiave API. Archivia questa chiave in un file .env nella directory del tuo progetto:

TINKER_API_KEY=your_key_here

Una volta configurato, la console Tinker diventa il tuo hub centrale per monitorare le esecuzioni di training e gestire i checkpoint.

Esecuzioni di training Tinker

La console Tinker traccia tutte le esecuzioni con dettagli chiave: ID univoci, modello base, rank LoRA e orari dell'ultima richiesta. Puoi cercare, filtrare e gestire più esperimenti da questa interfaccia.

Installa i pacchetti richiesti. Tinker ha dipendenze pari che non si installano automaticamente, quindi specificale esplicitamente:

pip install tinker transformers torch datasets python-dotenv numpy

I pacchetti transformers e torch forniscono tokenizzazione e operazioni sui tensori che Tinker usa internamente. La libreria datasets gestisce il caricamento dei dati di training da Hugging Face.

Ora imposta gli import e una funzione helper per il retry. Quando si fa training su server remoti, potresti vedere errori di connessione temporanei che non significano necessariamente che qualcosa non vada. Il tuo job di training sta ancora andando avanti sull'infrastruttura di Tinker. Questo wrapper di retry gestisce automaticamente quei problemi transitori:

import time
import numpy as np
from dotenv import load_dotenv
from datasets import load_dataset
import tinker
from tinker import types

def with_retry(future, max_attempts=3, delay=5):
    """Simple retry logic for API futures"""
    for attempt in range(max_attempts):
        try:
            return future.result()
        except Exception as e:
            if attempt == max_attempts - 1:
                raise
            time.sleep(delay)

Questa funzione prende un oggetto future (un'operazione ancora in corso) dall'API di Tinker e prova a ottenere il risultato fino a tre volte, attendendo cinque secondi tra un tentativo e l'altro. La maggior parte dei guasti temporanei si risolve entro questa finestra.

Passo 2: Inizializza il ServiceClient e carica il dataset

Il ServiceClient è il tuo punto d'ingresso a Tinker. Trova i modelli disponibili e gestisce l'autenticazione:

load_dotenv()
service_client = tinker.ServiceClient()

Non lo userai molto dopo questo setup iniziale. Serve principalmente a creare client di training e di sampling per modelli specifici.

Ora è il momento di caricare il dataset FinCoT.  Contiene domande finanziarie abbinate a catene di ragionamento passo-passo e risposte finali. Diversamente dai semplici dataset di Q&A, FinCoT insegna al modello a mostrare il ragionamento prima di fornire la risposta, una capacità importante nelle applicazioni di consulenza finanziaria in cui gli utenti devono capire il perché delle raccomandazioni.

dataset = load_dataset("TheFinAI/FinCoT")

Dataset Tinker

Il dataset include uno split SFT (supervised fine-tuning) con catene di ragionamento complete e uno split RL per il reinforcement learning. Usa lo split SFT per il training e campiona dallo split RL per la validazione per assicurarti che il modello generalizzi su esempi diversi.

train_data_raw = dataset["SFT"]  # 7,686 examples with reasoning chains
val_data_raw = dataset["RL"].shuffle(seed=42).select(range(500))  # 500 validation examples

print(f"Loaded {len(train_data_raw)} training examples")
print(f"Loaded {len(val_data_raw)} validation examples")

Passo 3: Crea il tuo client di training LoRA

Ora crea un client di training LoRA per Qwen3-8B:

training_client = service_client.create_lora_training_client(
    base_model="Qwen/Qwen3-8B",
    rank=32
)

Vediamo nel dettaglio cosa succede:

  • Qwen/Qwen3-8B: Un modello con 8 miliardi di parametri, abbastanza potente per compiti reali ma ancora efficiente da addestrare. Un buon compromesso per imparare. Puoi vedere tutti i modelli disponibili nella lineup dei modelli di Tinker.
  • LoRA (Low-Rank Adaptation): Invece di aggiornare tutti gli 8 miliardi di parametri, LoRA addestra piccoli strati "adapter" che modificano il comportamento del modello base. È come aggiungere lenti personalizzate a una fotocamera invece di ricostruirla. Il modello base resta congelato sui server di Tinker; alleni solo questi piccoli adapter.
  • rank=32: Controlla quanti parametri addestrabili contengono gli adapter LoRA. Un rank di 32 funziona bene per dataset con 5.000-10.000 esempi. Dataset più grandi potrebbero richiedere rank=64 o rank=128 per maggiore capacità di adattamento.

Per approfondire come funziona LoRA dal punto di vista matematico e come regolare il rank, consulta il primer su LoRA di Tinker o la guida completa di DataCamp su Low-Rank Adaptation.

I modelli linguistici non lavorano direttamente con le parole, ma con i "token", che rappresentano parti di testo. Un tokenizer scompone il testo in queste parti. Per esempio, "financial" potrebbe essere un token unico, mentre "understanding" potrebbe dividersi in "under" e "standing." 

Il tokenizer converte il testo in numeri (ID dei token) che il modello può elaborare. Recuperando il tokenizer dal client di training, ti assicuri di usare esattamente la stessa tokenizzazione attesa da Qwen3-8B.

tokenizer = training_client.get_tokenizer()

Passo 4: Trasforma i dati nel formato di Tinker

Tinker richiede i dati di training in un formato chiamato types.Datum. Questo formato separa l'input del modello dalla configurazione della loss function, dandoti controllo preciso su quali token contribuiscono al training.

Nel fine-tuning dei modelli linguistici, in genere vuoi che il modello impari a generare buone risposte, non a memorizzare le domande. Lo fai assegnando pesi diversi alle varie parti dell'input:

  • Token del prompt (la domanda): Peso = 0,0 (non imparare da questi)
  • Token della completion (la risposta): Peso = 1,0 (impara da questi)

Costruiamo la funzione di trasformazione dei dati passo dopo passo. Parti dalla firma della funzione:

def prepare_datum(example, max_length=10000):

Formatta la conversazione usando il template chat di Qwen3

Modelli diversi si aspettano formattazioni diverse per conversazioni multi-turno. Qwen3 usa token speciali come <|im_start|> e <|im_end|> per marcare i confini dei messaggi. Dividi ogni messaggio in parti di "osservazione" (intestazioni di ruolo) e "azione" (contenuto effettivo):

   # Qwen3 chat format - split into observation and action parts
    user_ob = "<|im_start|>user\n"
    user_ac = f"{example['Question']}<|im_end|>"
    
    assistant_ob = "\n<|im_start|>assistant\n"
    # Include both reasoning process AND final answer
    assistant_ac = f"{example['Reasoning_process']}\n\nFinal Answer: {example['Final_response']}<|im_end|>"

Il dataset FinCoT fornisce un ragionamento strutturato, quindi concateniamo i campi Reasoning_process e Final_response. Questo insegna al modello a mostrare il lavoro prima di rispondere.

Tokenizza ogni parte separatamente 

Per tracciare dove applicare i pesi, ciascuna parte del messaggio deve essere tokenizzata separatamente.

   # Tokenize each part separately to track weight boundaries
    user_ob_tokens = tokenizer.encode(user_ob, add_special_tokens=False)
    user_ac_tokens = tokenizer.encode(user_ac, add_special_tokens=False)
    assistant_ob_tokens = tokenizer.encode(assistant_ob, add_special_tokens=False)
    assistant_ac_tokens = tokenizer.encode(assistant_ac, add_special_tokens=False)
    
    # Combine all tokens
    all_tokens = user_ob_tokens + user_ac_tokens + assistant_ob_tokens + assistant_ac_tokens

Filtra le sequenze troppo lunghe

Alcuni esempi FinCoT includono catene di ragionamento estremamente dettagliate che superano la finestra di contesto confortevole di Qwen3-8B. Sebbene il modello supporti tecnicamente 32.768 token, sequenze molto lunghe possono causare instabilità nel training:

   # Check if sequence exceeds max length
    if len(all_tokens) > max_length:
        return None  # Skip this example

Filtrare gli esempi oltre i 10.000 token rimuove solo il 5-6% dei dati, ma migliora significativamente l'affidabilità del training.

Assegna i pesi per controllare da cosa impara il modello

Imposta pesi a 0,0 per la domanda (così il modello non impara a prevedere le domande) e a 1,0 per la risposta (così impara a generare buone risposte):

   # Weights: only train on assistant's answer (action part)
    weights = np.array(
        [0.0] * len(user_ob_tokens) +
        [0.0] * len(user_ac_tokens) +
        [0.0] * len(assistant_ob_tokens) +
        [1.0] * len(assistant_ac_tokens)
    )

Shifta token e pesi per la predizione del token successivo

I modelli linguistici predicono il token successivo in una sequenza. Quando il modello vede i token 0-9, dovrebbe predire il token 10. Quindi i target sono sempre l'input spostato avanti di una posizione:

   # CRITICAL: Shift tokens AND weights for next-token prediction
    input_tokens_model = all_tokens[:-1]
    target_tokens = all_tokens[1:]
    weights_shifted = weights[1:]

È importante notare che devi shiftare i pesi insieme ai token. Se non lo fai, la loss sarà disallineata: il modello verrebbe addestrato a prevedere il primo token della risposta, ma il peso in quella posizione sarebbe ancora 0,0 dal prompt. Questo bug di allineamento può impedire al modello di apprendere correttamente.

Restituisci i dati formattati

Infine, la funzione prepare_datum() restituisce i dati formattati, pronti per l'elaborazione.

   return types.Datum(
        model_input=types.ModelInput.from_ints(tokens=input_tokens_model),
        loss_fn_inputs=dict(weights=weights_shifted, target_tokens=target_tokens),
    )

Applicando la funzione definita ai sottoinsiemi grezzi di training e validazione, possiamo ora processare l'intero dataset:

# Process and filter training data
training_data_raw = [prepare_datum(example) for example in train_data_raw]
training_data = [d for d in training_data_raw if d is not None]
skipped = len(train_data_raw) - len(training_data)

print(f"Processed {len(training_data)} examples (skipped {skipped} too-long sequences)")
# Output: Processed 7,244 examples (skipped 442 too-long sequences)

# Process validation data
val_data_raw_processed = [prepare_datum(example) for example in val_data_raw]
val_data = [d for d in val_data_raw_processed if d is not None]

print(f"Validation set: {len(val_data)} examples")

Passo 5: Allena il modello con il training loop

Il training loop usa due delle primitive core dell'API di Tinker: forward_backward e optim_step. Costruiamolo passo dopo passo con il tracciamento della validazione per intercettare l'overfitting in anticipo.

Definisci una funzione di validation loss

Per prima cosa, definisci una funzione di validation loss. A differenza della training loss, la validation loss indica quanto bene il modello generalizza a dati non visti. È cruciale per rilevare l'overfitting.

def compute_validation_loss(val_data, batch_size=100):
    """Compute loss on validation set (forward only, no backward)"""
    batch_indices = np.random.choice(
        len(val_data), size=min(batch_size, len(val_data)), replace=False
    )
    batch = [val_data[i] for i in batch_indices]
    
    # Forward pass only (no backward!)
    fwd_future = training_client.forward(batch, loss_fn="cross_entropy")
    fwd_result = with_retry(fwd_future)
    
    # Calculate per-token loss
    loss_sum = fwd_result.metrics["loss:sum"]
    total_completion_tokens = sum(
        np.sum(np.array(val_data[i].loss_fn_inputs["weights"].data) > 0)
        for i in batch_indices
    )
    return loss_sum / total_completion_tokens if total_completion_tokens > 0 else 0

Il metodo forward() calcola la loss senza calcolare i gradienti, rendendolo efficiente per la valutazione, dove ti serve misurare le prestazioni, non aggiornare i pesi. Calcolare la validation loss aggiunge un sovraccarico minimo (è solo una forward pass, senza gradiente) ma fornisce un allarme precoce quando il gap tra training e validation loss supera 0,2, segnalando che il modello sta memorizzando invece di apprendere.

Configura i parametri di training

Dobbiamo definire il numero di epoche e il learning rate. LoRA richiede learning rate significativamente più alti rispetto al fine-tuning completo, tipicamente 10-20x maggiori.

# Training configuration
n_samples = len(training_data)  # 7,244
n_epochs = 4  # More epochs for smaller dataset
batch_size = 32

# Calculate optimal LR for Qwen3-8B with LoRA
# Formula: base_lr * lora_multiplier * (2000 / hidden_size) ** exponent
# For Qwen: base=5e-5, multiplier=10, hidden_size=4096, exponent=0.0775
learning_rate = 5e-5 * 10.0 * (2000 / 4096) ** 0.0775  # ≈ 4.7e-4

La formula tiene conto della dimensione del modello (Qwen3-8B ha una hidden size di 4.096) e usa esponenti determinati empiricamente che variano per famiglia di modelli, come documentato nel Tinker cookbook. Per i modelli Qwen, l'esponente è 0,0775, mentre i modelli Llama usano 0,781. Nel nostro caso, produce un learning rate di circa 4,7e-4, garantendo convergenza stabile senza esplosioni di gradiente.

Imposta il warmup del learning rate

Partire con il learning rate pieno può causare esplosioni di gradiente nelle fasi iniziali quando il modello non si è ancora adattato al task. Il warmup aumenta gradualmente il learning rate da quasi zero al valore target nelle prime 200 iterazioni. Lo si vede chiaramente nella visualizzazione del training di prima, dove il learning rate (linea verde) sale gradualmente nelle prime 200 iterazioni.

# Warmup configuration
warmup_steps = 200

# Calculate iterations from epochs
num_iterations = n_epochs * (n_samples // batch_size)  # 4 * 226 = 904
checkpoint_interval = 200
validation_interval = 50

Avvia il training loop

Dopo aver inizializzato le liste per tracciare le loss, possiamo avviare il training loop:

losses = []
per_token_losses = []
val_losses = []

for iteration in range(num_iterations):
    # Sample random batch
    batch_indices = np.random.choice(len(training_data), size=batch_size, replace=False)
    batch = [training_data[i] for i in batch_indices]
    
    # Apply learning rate warmup
    if iteration < warmup_steps:
        current_lr = learning_rate * (iteration + 1) / warmup_steps
    else:
        current_lr = learning_rate

La logica di warmup aumenta gradualmente il learning rate durante le prime 200 iterazioni, prevenendo instabilità iniziali.

Chiama le primitive core di training di Tinker

A ogni iterazione, le primitive core di Tinker vengono chiamate così:

   # API Primitive 1: forward_backward - compute gradients
    fwdbwd_future = training_client.forward_backward(batch, loss_fn="cross_entropy")

    # API Primitive 2: optim_step - update parameters
    optim_future = training_client.optim_step(types.AdamParams(learning_rate=current_lr))

    # Wait for results with retry logic
    fwdbwd_result = with_retry(fwdbwd_future)
    optim_result = with_retry(optim_future)

forward_backward calcola i gradienti determinando quanto siano errate le previsioni del modello e in quale direzione regolare ogni parametro. Usa la cross-entropy loss, che misura quanto bene la distribuzione di probabilità prevista dal modello corrisponde ai token successivi reali. optim_step aggiorna effettivamente i parametri del modello in base a quei gradienti usando l'ottimizzatore Adam.

Nota come entrambi i metodi restituiscano subito oggetti "future". Questo permette a Tinker di elaborare più operazioni in parallelo, accelerando i tempi. Chiamando with_retry(future) attendi che quella specifica operazione termini.

Traccia e mostra le metriche di loss

Calcoliamo sia la loss totale che quella per token e le aggiungiamo alle rispettive liste:

   # Track loss
    loss_sum = fwdbwd_result.metrics["loss:sum"]
    total_completion_tokens = sum(
        np.sum(np.array(training_data[i].loss_fn_inputs["weights"].data) > 0)
        for i in batch_indices
    )
    per_token_loss = loss_sum / total_completion_tokens

    losses.append(loss_sum)
    per_token_losses.append(per_token_loss)

    if iteration % 10 == 0:
        warmup_indicator = "🔥" if iteration < warmup_steps else ""
        print(f"{warmup_indicator} Iteration {iteration} | Train: {per_token_loss:.4f} | LR: {current_lr:.6f}")

La loss per token normalizza la loss per il numero di token di risposta, rendendo più facile interpretare i risultati tra batch size diversi. Un buon training in genere parte con loss per token intorno a 1,2-1,5 e scende sotto 0,8 al checkpoint-400. Se la loss parte sopra 2,0 o non scende dopo 50 iterazioni, controlla la preparazione dei dati e il learning rate.

Aggiungi controlli periodici di validazione e checkpoint

Salvare i pesi ogni 200 iterazioni crea snapshot da confrontare. Come visto nei risultati di training, il checkpoint-400 generalizza meglio del checkpoint finale nonostante una loss di training più alta. Salva sempre più checkpoint e valutali su dati tenuti da parte. I checkpoint restano sui server di Tinker dopo la fine dello script.

   # Compute validation loss periodically
    if iteration % validation_interval == 0:
        val_loss = compute_validation_loss(val_data)
        val_losses.append((iteration, val_loss))
        gap = val_loss - per_token_loss
        print(f"  📊 Iteration {iteration} | Train: {per_token_loss:.4f} | Val: {val_loss:.4f} | Gap: {gap:+.4f}")

    # Checkpoint every 200 iterations
    if iteration > 0 and iteration % checkpoint_interval == 0:
        training_client.save_weights_for_sampler(name=f"fincot-checkpoint-{iteration}")

Checkpoint Tinker

Tinker organizza i checkpoint in due categorie: checkpoint full-state (per riprendere il training se interrotto) e pesi del sampler (checkpoint leggeri per l'inferenza). Nota che il checkpoint-400 appare in entrambi i formati, rendendo facile sia continuare il training sia distribuire il modello.

Se il tuo training viene interrotto, puoi riprendere da qualsiasi checkpoint salvato usando i pesi full-state, continuando senza ricominciare da capo.

Per la reference completa dell'API su questi metodi, vedi la documentazione di training e sampling di Tinker.

Passo 6: Salva il tuo modello fine-tuned

Al termine del training, salva i pesi finali del modello:

sampling_client = training_client.save_weights_and_get_sampling_client(
    name="financial-qa-qwen3-8b-lora"
)

I pesi vengono salvati sui server di Tinker, non sul tuo computer. Gli adapter LoRA richiedono tipicamente solo decine di megabyte. Tinker può opzionalmente esportarli per l'uso con provider di inferenza esterni se vuoi distribuire il modello al di fuori della loro infrastruttura.

Passo 7: Metti alla prova il modello con domande di esempio

Infine, testa il tuo modello fine-tuned usando la primitiva API sample. Per prima cosa, configura i parametri di sampling:

sampling_params = types.SamplingParams(
    max_tokens=200,
    temperature=0.7,
    top_p=0.9,
    stop_sequences=["<|im_end|>"]
)

Spieghiamo questi parametri:

  • max_tokens: Lunghezza massima della risposta generata (200 token corrispondono a circa 150 parole).

  • temperature: Controlla la casualità nella generazione. Valori più bassi (0,1-0,5) rendono l'output più focalizzato e deterministico; valori più alti (0,7-1,0) permettono maggiore creatività. Un valore di 0,7 bilancia coerenza e varietà.

  • top_p (nucleus sampling): Considera solo l'insieme più piccolo di token la cui probabilità cumulativa supera questo valore. 0,9 significa "considera solo i token che insieme rappresentano il 90% della massa di probabilità". Evita che il modello scelga token molto improbabili.

  • stop_sequences: Indica al modello di fermarsi quando incontra questa sequenza. <|im_end|> è il marcatore di fine sequenza.

Genera risposte per domande di test:

questions = [
    "What are the main risks associated with investing in stocks?",
    "How does diversification help reduce portfolio risk?"
]

for question in questions:
    model_input = types.ModelInput.from_ints(tokenizer.encode(question))

    response = with_retry(
        sampling_client.sample(
            prompt=model_input,
            num_samples=1,
            sampling_params=sampling_params
        )
    )

    answer = tokenizer.decode(response.sequences[0].tokens)
    print(f"Q: {question}")
    print(f"A: {answer}\n")

Ecco due output di esempio del modello fine-tuned (troncati per illustrazione):

Q: What are the main risks associated with investing in stocks?
A: Investing in stocks carries several key risks. Market risk represents the potential for stock prices to decline due to economic downturns or market volatility. Company-specific risk arises from poor management decisions, competitive pressures, or operational failures that affect individual stocks. Liquidity risk occurs when you cannot sell shares quickly without significantly impacting the price. Additionally, investors face inflation risk, where returns may not keep pace with rising prices...

Q: How does diversification help reduce portfolio risk?
A: Diversification reduces portfolio risk by spreading investments across different asset classes, sectors, and geographic regions. When you hold multiple uncorrelated assets, losses in one investment can be offset by gains in others. For example, if technology stocks decline but healthcare stocks rise, a diversified portfolio experiences less volatility than one concentrated in technology alone. The key principle is that different assets respond differently to market conditions...

L'oggetto response contiene una lista sequences perché puoi richiedere più campioni per prompt. Accedi ai token tramite sequences[0].tokens e decodificali in testo usando lo stesso tokenizer.

Hai ora dimostrato con successo tutte e quattro le primitive API principali:

  1. forward_backward: Calcola i gradienti dai tuoi dati di training

  2. optim_step: Aggiorna i parametri del modello con l'ottimizzatore Adam

  3. save_weights_and_get_sampling_client: Persisti il tuo modello fine-tuned

  4. sample: Genera predizioni dal modello fine-tuned

Questo stesso schema di workflow si estende a paradigmi di training più avanzati. Per reinforcement learning o preference optimization, useresti le stesse primitive ma con loss function e formati dati diversi. Se ti interessa allineare i modelli alle preferenze umane, esplora le tecniche di preference fine-tuning, che si basano sugli stessi fondamenti. Il Tinker cookbook fornisce esempi per questi scenari avanzati.

Ma come fai a sapere se il fine-tuning ha davvero migliorato le prestazioni? Invece di affidarti a impressioni soggettive, serve una valutazione rigorosa. Nella prossima sezione, vedrai i risultati di una valutazione LLM-as-judge che confronta il modello fine-tuned con il Qwen3-8B base su 10 domande finanziarie diverse.

Valutare il modello fine-tuned con un LLM judge

Per misurare le prestazioni in modo oggettivo, abbiamo usato LLM-as-judge: GPT-4o ha valutato il checkpoint-400 rispetto al Qwen3-8B base su 10 domande finanziarie diverse, assegnando punteggi a ogni risposta per accuratezza, chiarezza, completezza e terminologia finanziaria.

Il modello fine-tuned ha ottenuto 8,5/10, contro 6,5 del modello base, vincendo tutti e 10 i confronti. Le motivazioni chiariscono il perché: fornisce spiegazioni complete con ragionamento strutturato e sicuro, mentre il base esita con "I think" e "maybe", lasciando i pensieri incompleti.

Questo conferma le migliori prestazioni reali del checkpoint-400. La loss più bassa al training all'iterazione 900 ci avrebbe dato memorizzazione, non ragionamento.

Consulta lo script di confronto e i risultati dettagliati per eseguire valutazioni simili sui tuoi modelli.

Conclusione

Hai visto come Tinker semplifica il fine-tuning senza sacrificare il controllo. Quattro primitive API core, forward_backward, optim_step, save_weights_and_get_sampling_client e sample, ti danno i mattoni per workflow di training personalizzati mentre Tinker gestisce l'infrastruttura distribuita.

Il modello di Q&A finanziario che abbiamo costruito dimostra le migliori pratiche di produzione: il tracciamento della validazione ha intercettato presto l'overfitting, il checkpoint-400 ha superato il modello finale puntando sulla generalizzazione, e la valutazione con LLM-as-judge ha confermato un miglioramento di 2 punti rispetto al modello base. Questi pattern valgono sia che tu faccia fine-tuning di modelli da 8B sia da 70B parametri.

La piattaforma è in beta con accesso su waitlist, ma i concetti che hai imparato si applicano direttamente non appena avrai accesso. Le esecuzioni di training che ho fatto per questo tutorial (incluse quelle fallite e sperimentali) sono costate 150 crediti iniziali: con i crediti di partenza puoi eseguire questo tutorial 6 volte più esperimenti aggiuntivi, più che sufficienti per imparare e iterare sui tuoi progetti di fine-tuning.

E adesso? Prova a fare fine-tuning su dati specifici del tuo dominio, sperimenta con rank LoRA e learning rate diversi, oppure esplora paradigmi avanzati come il reinforcement learning from human feedback. Il Tinker cookbook offre esempi per questi scenari. Inizia a pensare al tuo caso d'uso ora—quale comportamento vuoi che il tuo modello impari?

Se vuoi sviluppare competenze più ampie nell'addestramento e nel fine-tuning dei modelli AI più recenti per la produzione, dai un'occhiata al percorso di carriera Associate AI Engineer for Developers.


Bexruz (Bex) Tuychiev's photo
Author
Bexruz (Bex) Tuychiev
LinkedIn

Sono un creator di contenuti sulla data science con oltre 2 anni di esperienza e uno dei più seguiti su Medium. Mi piace scrivere articoli dettagliati su AI e ML con un pizzico di sarcasmo, perché bisogna pur fare qualcosa per renderli un po’ meno noiosi. Ho pubblicato oltre 130 articoli e un corso su DataCamp, con un altro in arrivo. I miei contenuti sono stati visti da oltre 5 milioni di persone, 20.000 delle quali sono diventate follower sia su Medium che su LinkedIn. 

Argomenti
Intelligenza artificiale
Large Language Models
AI generativa

Corsi di AI Engineering

Programma

Ingegnere AI associato per sviluppatori

26 ore
Scopri come integrare l'intelligenza artificiale nelle applicazioni software utilizzando API e librerie open-source. Inizia oggi il tuo percorso per diventare un ingegnere AI!
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

Mostra AltroMostra Altro