Eseguire un modello gated di HuggingFace su una GPU che vuoi tu sul cloud Modal.com
Se sei curioso di capire come scaricare un modello gated da Hugging Face, farlo girare su una GPU in cloud e ottenere testo a partire da un semplice prompt, questo post è per te.
Ti guiderò passo-passo, spezzettando il codice in “pezzi Lego” e spiegando perché ogni riga è necessaria, quali alternative puoi provare e come evitare gli errori più comuni.
Nota: il modello usato nell’esempio è Qwen/Qwen3.8-Flash-Next, ma il procedimento vale per qualunque modello presente su Hugging Face.
🎯 Obiettivi del tutorial
Capire le parti fondamentali di un’app Modal: configurare dipendenze, secret (token) e GPU.
Scrivere la funzione di inference che genera testo.
Eseguire il tutto in locale oppure in un notebook Jupyter.
Sperimentare con varianti: CPU, modelli più piccoli, parametri di generazione, ecc.
1. Dipendenze e import – “Che roba devo installare?”
import os # legge le variabili d’ambiente (es. il token HF)
import modal # SDK di Modal (client, decoratori, ecc.)
from modal import Image # costruisce l’immagine Docker
🎯 Quick hint
modal è la libreria che ti permette di dichiarare funzioni remote, immagini Docker e secret senza scrivere un Dockerfile.
È il servizio dove puoi far girare i modelli da testare, configurando le GPU da utilizzare.
Prima visita: https://modal.com
Registrati creando un account.
In alto a destra clicca sul tuo avatar e scegli “Settings” (Impostazioni).
Nella barra laterale sinistra seleziona “API Tokens”, poi premi “New Token” (Nuovo token).
Sul tuo PC esegui:
modal token set --token-id ak-qualcosaqui --token-secret as-qualcosaqui
2. Configurazione dell’app Modal – “Diamo un nome al progetto”
app = modal.App("app Modal") # Nome dell’app (può essere qualsiasi stringa)
Un modal.App è un workspace che raggruppa funzioni, secret e immagini.
Il nome è solo un’etichetta leggibile da te; scegli qualcosa che ti ricordi facilmente, per esempio:
app = modal.App("mia-gpt-demo")
2.1 Definizione dell’immagine Docker
image = (
Image.debian_slim()
.pip_install(
"torch", # PyTorch → inferenza dei modelli
"transformers", # 🤗 Transformers → API high-level
"accelerate", # gestione device-mapping (GPU/CPU)
)
)
🎯 Quick hint
Image.debian_slim() è la base leggera (Debian Slim) sulla quale installeremo le librerie Python necessarie: torch, transformers e accelerate.
Declarativo: non devi scrivere un Dockerfile; Modal lo crea in background.
Cache: la prima volta che avvii lo script, Modal scarica tutti i pacchetti e li mette in cache. Le esecuzioni successive partono quindi molto più velocemente. 🚀
2.2 Secret con il token di Hugging Face
secrets = [modal.Secret.from_name("huggingface-token")]
🎯 Quick hint
Una Secret è un dizionario cifrato di chiavi/valori, per esempio HF_TOKEN=hf_XXXXX.
Viene iniettata come variabile d’ambiente dentro il container remoto.
Perché usare due chiavi?
transformers nelle versioni più recenti legge HUGGINGFACE_HUB_TOKEN, mentre le versioni più vecchie cercano HF_TOKEN.
Inserendole entrambe garantiamo la massima compatibilità.
2.3 GPU e nome del modello
GPU = "T4" # tipo di GPU da richiedere
MODEL_NAME = "Qwen/Qwen3.8-Flash-Next" # percorso “owner/repo” su Hugging Face
🎯 Quick hint
GPU è opzionale: se la tua workspace non ha quota GPU, commenta gpu=GPU più avanti.
MODEL_NAME è il percorso completo che transformers.from_pretrained() comprende, per esempio:
MODEL_NAME = "meta-llama/Llama-3.2-3B"
3. La funzione di inference – “Il cuore dell’app”
@app.function(
image=image,
secrets=secrets,
gpu=GPU, # rimuovi se usi solo CPU
timeout=1800, # 30 min max per l’esecuzione
)
def generate(prompt: str) -> str:
3.1 Controllo del token
hf_token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")
if hf_token is None:
raise RuntimeError(
"Nessun token HF trovato – controlla la secret `huggingface-token`."
)
Fail-fast: se il token manca, lo script si ferma subito invece di fallire più tardi durante il download del modello.
3.2 Import delle librerie nel container
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seed
🎯 Quick hint
L’import avviene dentro la funzione perché il codice viene eseguito nel container remoto, non sul tuo PC.
3.3 Caricamento del tokenizer
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
AutoTokenizer.from_pretrained() scarica il tokenizer dal Hub.
Impostiamo pad_token e padding_side per evitare errori di allineamento, anche se in questo esempio eseguiamo una sola frase.
3.4 Caricamento del modello
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
device_map="auto", # auto-sharding su GPU/CPU
)
device_map="auto" distribuisce i pesi tra GPU e CPU se il modello è troppo grande per essere caricato interamente sulla GPU.
3.5 Generazione del testo
set_seed(42) # risultato deterministico (utile per debugging)
inputs = tokenizer.encode(
prompt,
return_tensors="pt"
).to("cuda")
outputs = model.generate(
inputs,
max_new_tokens=50
)
return tokenizer.decode(
outputs[0],
skip_special_tokens=True
)
.to("cuda") sposta il tensore sulla GPU; se usi solo CPU, sostituiscilo con .to("cpu").
max_new_tokens=50 è il numero massimo di token generati. Puoi alzarlo a 200 o più, a seconda del caso d’uso.
skip_special_tokens=True rimuove token speciali come <s>, </s> e simili dal risultato finale.
4. Esecuzione locale (o in notebook) – “Mettiamolo in moto”
if __name__ == "__main__":
# stampa automatica del risultato di .remote()
with modal.enable_output():
# avvia il workspace temporaneo
with app.run():
result = generate.remote(
"Never gonna give you up, never gonna"
)
print("\nGenerated text:\n", result)
4.1 Perché app.run()?
Crea il workspace temporaneo dove verranno spinte le funzioni remote.
Dentro il blocco with, qualsiasi funzione decorata con @app.function è pronta per essere chiamata.
4.2 modal.enable_output()
È una comodità: ogni chiamata .remote() stampa automaticamente il valore restituito sul terminale o nel notebook.
Se non lo usi, .remote() restituisce un ModalFuture (oggetto asincrono) e dovrai chiamare .get() per ottenere il risultato.
4.3 .remote() vs .remote().get()
Metodo | Cosa restituisce | Quando usarlo |
generate.remote(prompt) | Stringa (se dentro enable_output) o ModalFuture | In notebook con enable_output() |
generate.remote(prompt).get() | Stringa (blocca finché il future è risolto) | Quando vuoi gestire manualmente eccezioni o non usi enable_output() |
Errore comune: chiamare .get() su una stringa, perché il risultato è già materializzato da enable_output().
5. Flusso completo – “Dalla preparazione all’output”
5.1 Pre-setup (una tantum)
Crea la secret con il token:
modal secret create huggingface-token HF_TOKEN=hf_XXXXX HUGGINGFACE_HUB_TOKEN=hf_XXXXX
Richiedi l’accesso al modello gated su Hugging Face cliccando “Request access”.
Facoltativamente, configura il token di Modal con:
modal token set --token-id ... --token-secret ...
5.2 Esecuzione dello script
Il file:
definisce app, immagine, secret, ecc.;
entra nel blocco if name == "__main__":;
avvia modal.enable_output() e app.run();
esegue generate.remote(prompt), inviando una richiesta al cluster Modal.
A questo punto Modal:
crea un container con l’immagine Docker definita;
inietta le variabili d’ambiente (HF_TOKEN, HUGGINGFACE_HUB_TOKEN);
controlla il token;
scarica tokenizer e modello;
carica il modello su GPU/CPU;
genera il testo;
restituisce la stringa.
modal.enable_output() stampa il risultato; la variabile result lo contiene anche per eventuali utilizzi successivi.
5.3 Output finale
Vedrai qualcosa del genere:
Generated text:
Never gonna give you up, never gonna ...
6. Possibili variazioni e consigli pratici
Scopo | Come modificare il codice |
Testare su CPU | Commenta gpu=GPU nella decorazione e cambia .to("cuda") → .to("cpu"). |
Ridurre il tempo di download | Usa un modello più piccolo, per esempio facebook/opt-125m, oppure verifica la cache locale (~/.cache/huggingface). |
Aumentare la lunghezza della risposta | Cambia max_new_tokens=50 → max_new_tokens=200 (o più). |
Gestire eccezioni | Avvolgi la chiamata in try/except e usa .remote().get() per catturare modal.exceptions.RemoteError. |
Parametri di generazione avanzati | Aggiungi temperature, top_p, do_sample, ecc. |
Aggiungere altri segreti | secrets = [modal.Secret.from_name("huggingface-token"), modal.Secret.from_name("my-other-secret")] |
Parallelizzare più prompt | Definisci una funzione batch_generate(prompts: List[str]) e sfrutta modal.map per eseguire le richieste in parallelo. |
Esempio: parametri di generazione avanzati
outputs = model.generate(
inputs,
max_new_tokens=100,
temperature=0.7,
top_p=0.9,
)
7. Riepilogo – “Cosa fa ogni riga?”
import os, modal
from modal import Image # 0️⃣ import necessari
app = modal.App("qwen-gated") # 1️⃣ workspace
image = ( # 1.1️⃣ immagine Docker con pip install
Image.debian_slim()
.pip_install(
"torch",
"transformers",
"accelerate"
)
)
secrets = [
modal.Secret.from_name("huggingface-token")
] # 1.2️⃣ token HF
GPU = "T4" # 1.3️⃣ GPU scelta
MODEL_NAME = "Qwen/Qwen3.8-Flash-Next" # 1.3️⃣ modello da scaricare
@app.function(
image=image,
secrets=secrets,
gpu=GPU,
timeout=1800
) # 2️⃣ decoratore
def generate(prompt: str) -> str: # funzione remota
hf_token = (
os.getenv("HF_TOKEN")
or os.getenv("HUGGINGFACE_HUB_TOKEN")
)
if hf_token is None:
raise RuntimeError(
"Nessun token HF trovato …"
)
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
set_seed
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
device_map="auto"
)
set_seed(42)
inputs = tokenizer.encode(
prompt,
return_tensors="pt"
).to("cuda")
outputs = model.generate(
inputs,
max_new_tokens=50
)
return tokenizer.decode(
outputs[0],
skip_special_tokens=True
)
if __name__ == "__main__": # 3️⃣ avvio script
with modal.enable_output():
with app.run():
result = generate.remote(
"Never gonna give you up, never gonna"
)
print(
"\nGenerated text:\n",
result
)
8. Checklist finale – “Sono pronto a far girare tutto?”
✅ Verifica | Descrizione |
Token Hugging Face | Creato con scope read e inserito in una secret chiamata huggingface-token. |
Chiavi nella secret | Contiene almeno HF_TOKEN oppure HUGGINGFACE_HUB_TOKEN. |
Accesso al modello gated | Hai cliccato “Request access” → “You have access”. |
Quota GPU (opzionale) | Se usi una GPU, la workspace possiede una quota T4; altrimenti commenta gpu=GPU. |
Python 3.10+ e Modal installato | pip install modal (e modal token set se usi la CLI). |
Secret corretta | modal secret list mostra huggingface-token. |
Docker cache | La prima esecuzione scarica circa 5–10 GB; le successive usano la cache. |
Se tutti questi punti sono a posto, premi Run (oppure esegui python my_script.py) e guarda il tuo modello parlare! 🎉
🎉 Conclusioni
Modal ti permette di trasformare un semplice script Python in un servizio cloud pronto a scalare, senza scrivere Dockerfile o gestire server.
Con pochi passi — definire immagine, secret, GPU e funzione — puoi scaricare e usare modelli gated di Hugging Face, anche quelli da 3–4 GB.
Il codice è modularissimo: basta cambiare MODEL_NAME, il valore di max_new_tokens o aggiungere parametri per sperimentare molto rapidamente.
Prossimi passi consigliati: Crea una piccola API (con @app.function + FastAPI) per far parlare il modello via web. Prova a fare batch inference su una lista di prompt e osserva le performance su GPU vs CPU. Sperimenta con modelli di generazione immagini (Stable Diffusion) usando lo stesso schema Modal.





Commenti