Ogni volta che devo sperimentare con un nuovo modello LLM, apro Jupyter. Non VS Code, non uno script Python, Jupyter. C'e qualcosa nell'esecuzione cella per cella che si sposa perfettamente con il workflow esplorativo tipico del lavoro con i language model.
Il problema e che configurare un ambiente Jupyter per LLM non e banale come installare un paio di pacchetti. Tra CUDA, quantizzazione, gestione della memoria GPU, e le mille dipendenze dei framework moderni, e facile perdersi. Questo articolo e il setup che avrei voluto trovare quando ho iniziato.
Perche Jupyter funziona bene con gli LLM
Il motivo principale e semplice: caricare un modello richiede tempo. Con Llama 3 8B parliamo di 20-30 secondi, con modelli piu grandi anche minuti. In uno script tradizionale, ogni volta che modifichi qualcosa devi ricaricare tutto. In Jupyter carichi il modello una volta e poi sperimenti quanto vuoi.
Questo cambia completamente il modo di lavorare. Puoi testare dieci prompt diversi in un minuto, modificare parametri al volo, confrontare output side by side. Per il prompt engineering e fondamentale.
C'e anche il vantaggio della documentazione integrata. Le celle markdown ti permettono di annotare cosa stai provando e perche. Quando torni su un notebook dopo un mese, capisci subito cosa stavi facendo. Con gli script puri, buona fortuna a ricordarti il contesto.
Preparare l'ambiente
Prima regola: isola sempre le dipendenze. I pacchetti per LLM hanno requisiti specifici e spesso conflittuali. Un virtual environment dedicato ti evita mal di testa.
# preferisco conda per progetti con CUDA
conda create -n llm python=3.11
conda activate llm
Puoi usare anche venv, ma conda gestisce meglio le dipendenze CUDA. Se sei su Mac con Apple Silicon, venv va benissimo.
Installa Jupyter Lab (non il notebook classico, Lab e nettamente superiore come esperienza):
pip install jupyterlab
Poi le dipendenze per LLM. Questo e il mio setup base:
pip install torch torchvision torchaudio
pip install transformers accelerate
pip install bitsandbytes # per quantizzazione
pip install ollama # se usi Ollama
Se hai bisogno di LangChain per RAG o chain complesse:
pip install langchain langchain-community
Verificare che la GPU funzioni
La prima cella di ogni mio notebook LLM e questa:
import torch
print(f"PyTorch: {torch.__version__}")
print(f"CUDA disponibile: {torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")
Se CUDA non risulta disponibile ma hai una GPU NVIDIA, il problema e quasi sempre una versione di PyTorch incompatibile con i tuoi driver. La soluzione:
pip uninstall torch
pip install torch --index-url https://download.pytorch.org/whl/cu121
Sostituisci cu121 con la versione CUDA che hai installato (controlla con nvidia-smi).
Per monitorare l'uso della GPU durante l'esecuzione, questa funzione e utile:
import subprocess
def vram_status():
result = subprocess.run(
['nvidia-smi', '--query-gpu=memory.used,memory.total', '--format=csv,nounits,noheader'],
capture_output=True, text=True
)
used, total = map(int, result.stdout.strip().split(', '))
print(f"VRAM: {used} MB / {total} MB ({used/total*100:.1f}%)")
La chiamo prima e dopo operazioni pesanti per capire quanto sta consumando il modello.
Caricare modelli
Ci sono diversi modi per caricare un LLM in Jupyter. Il piu comune usa Hugging Face Transformers:
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model_name = "meta-llama/Meta-Llama-3-8B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
Il problema e che questo carica il modello in FP16, che per Llama 3 8B significa circa 16 GB di VRAM. Se non hai abbastanza memoria, devi quantizzare:
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_quant_type="nf4"
)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=quantization_config,
device_map="auto"
)
Con la quantizzazione 4-bit, Llama 3 8B sta comodamente in 6 GB di VRAM. La qualita cala leggermente, ma per la maggior parte dei task non te ne accorgi.
Se usi Ollama, e tutto piu semplice:
import ollama
response = ollama.generate(
model='llama3',
prompt='Spiega cosa fa questo codice...'
)
print(response['response'])
Ollama gestisce tutto internamente - quantizzazione, VRAM, cache. Perdi un po' di controllo ma guadagni in semplicita.
Una funzione di generazione decente
Questa e la funzione che uso nella maggior parte dei miei notebook:
def genera(prompt, max_tokens=512, temperature=0.7, system_prompt="Sei un assistente utile."):
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_tokens,
temperature=temperature,
do_sample=True,
top_p=0.9,
pad_token_id=tokenizer.eos_token_id
)
# decodifica solo la parte generata
response = tokenizer.decode(
outputs[0][inputs['input_ids'].shape[1]:],
skip_special_tokens=True
)
return response
Poi la uso cosi:
risposta = genera("Cos'e un container Docker?")
print(risposta)
La cosa comoda e che posso modificare i parametri al volo per sperimentare:
# piu creativo
genera("Scrivi una storia breve", temperature=0.9)
# piu deterministico
genera("Spiega TCP/IP", temperature=0.3)
Gestire la memoria
La VRAM e preziosa e i modelli LLM ne mangiano parecchia. Quando hai finito con un modello e vuoi caricarne un altro, devi liberare la memoria esplicitamente:
import gc
def libera_memoria():
global model, tokenizer
del model
del tokenizer
gc.collect()
torch.cuda.empty_cache()
Se non lo fai, ti ritrovi con errori di memoria anche se teoricamente hai spazio. PyTorch tiene la memoria allocata finche non gliela chiedi indietro.
Per progetti dove devo confrontare piu modelli, ho scritto una classe che gestisce il caricamento/scaricamento:
class GestoreModelli:
def __init__(self):
self.model = None
self.tokenizer = None
self.nome_corrente = None
def carica(self, nome_modello, quantizza=True):
if self.nome_corrente == nome_modello:
return # gia caricato
if self.model:
del self.model
del self.tokenizer
gc.collect()
torch.cuda.empty_cache()
print(f"Carico {nome_modello}...")
self.tokenizer = AutoTokenizer.from_pretrained(nome_modello)
if quantizza:
config = BitsAndBytesConfig(load_in_4bit=True)
self.model = AutoModelForCausalLM.from_pretrained(
nome_modello,
quantization_config=config,
device_map="auto"
)
else:
self.model = AutoModelForCausalLM.from_pretrained(
nome_modello,
torch_dtype=torch.float16,
device_map="auto"
)
self.nome_corrente = nome_modello
print("Caricato.")
gestore = GestoreModelli()
gestore.carica("meta-llama/Meta-Llama-3-8B-Instruct")
# ... usa il modello ...
gestore.carica("mistralai/Mistral-7B-Instruct-v0.2") # scarica il precedente automaticamente
Google Colab come alternativa
Se non hai una GPU locale, Colab e un'opzione valida. La versione gratuita ti da una T4 con 16 GB di VRAM, sufficiente per la maggior parte dei modelli 7-8B.
Setup tipico per Colab:
# verifica GPU
!nvidia-smi
# installa dipendenze (il ! esegue comandi shell)
!pip install -q transformers accelerate bitsandbytes
# monta Drive per salvare modelli (eviti di riscaricare ogni volta)
from google.colab import drive
drive.mount('/content/drive')
Il limite principale di Colab gratuito e la durata della sessione. Dopo 12 ore (o prima se inattivo) perdi tutto. Colab Pro estende a 24 ore e da accesso a GPU migliori.
Un trucco: salva i modelli quantizzati su Google Drive dopo il primo download. Al prossimo avvio li carichi da li invece di riscaricare da Hugging Face.
# salva
model.save_pretrained("/content/drive/MyDrive/modelli/llama3-8b-4bit")
tokenizer.save_pretrained("/content/drive/MyDrive/modelli/llama3-8b-4bit")
# carica dalla prossima volta
model = AutoModelForCausalLM.from_pretrained("/content/drive/MyDrive/modelli/llama3-8b-4bit")
Struttura che uso per i progetti
Dopo tanti esperimenti, ho trovato una struttura che funziona bene:
progetto/
├── 01-setup.ipynb # caricamento modello, verifiche
├── 02-esperimenti.ipynb # test vari, prompt engineering
├── 03-valutazione.ipynb # metriche, confronti
└── risultati/ # output salvati
Il primo notebook lo eseguo una volta per caricare il modello. Gli altri li uso per sperimentare. Tenendo il modello caricato nel kernel, posso saltare tra notebook senza ricaricare ogni volta.
Per salvare i risultati degli esperimenti:
import json
from datetime import datetime
def salva_esperimento(nome, prompt, risposta, parametri):
esperimento = {
'timestamp': datetime.now().isoformat(),
'nome': nome,
'prompt': prompt,
'risposta': risposta,
'parametri': parametri
}
filename = f"risultati/{nome}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(filename, 'w') as f:
json.dump(esperimento, f, indent=2, ensure_ascii=False)
Sembra overkill, ma quando dopo un mese devi capire quale combinazione di parametri dava i risultati migliori, ringrazierai te stesso.
Estensioni utili
Alcune estensioni di Jupyter Lab che mi hanno semplificato la vita:
pip install jupyterlab-nvdashboard # monitoraggio GPU integrato
pip install jupyterlab-git # git senza uscire da Jupyter
Il dashboard GPU e particolarmente utile. Vedi in tempo reale quanto stai usando la VRAM senza dover aprire un terminale separato con nvidia-smi.
Conclusioni
Jupyter non e l'unico modo per lavorare con LLM, ma per la fase di esplorazione e sperimentazione rimane il mio preferito. La possibilita di iterare velocemente, documentare al volo, e visualizzare i risultati nello stesso posto vale la complessita del setup iniziale.
Il consiglio che do sempre: investi tempo nel configurare bene l'ambiente una volta. Crea un template di notebook con le funzioni che usi sempre, salvalo, e usalo come punto di partenza per ogni nuovo progetto. Il tempo che risparmi sul lungo periodo e enorme.
E se sei su Colab perche non hai GPU, non vergognarti. Ho fatto progetti complessi interamente su Colab gratuito. L'importante e capire i limiti e lavorarci intorno.