Gemma 3 270M: guida con progetto demo (app Android)
Scopri come configurare, integrare ed eseguire il modello Gemma 3 270M in un'app Android, insieme alle best practice per garantirne affidabilità e sicurezza per l'inferenza on-device.
Google ha rilasciato di recente Gemma 3 270M, un modello ultracompatto da 270 milioni di parametri che rappresenta un nuovo paradigma per il deployment efficiente dell'IA. A differenza dei modelli generali di grandi dimensioni, Gemma 3 270M è pensato per il fine-tuning su compiti specifici mantenendo, già out-of-the-box, la capacità di seguire istruzioni.
In questo tutorial ti mostro come iniziare con Gemma 3 270M e perché è un'ottima scelta per gli sviluppatori che cercano:
Inferenza fulminea con minimo overhead computazionale
Efficienza energetica estrema (consuma solo lo 0,75% di batteria per 25 conversazioni su Pixel 9 Pro)
Quantizzazione con precisione INT4 e perdita di performance minima
Privacy totale con esecuzione completamente on-device
Teniamo i nostri lettori aggiornati sulle ultime novità dell'IA con The Median, la nostra newsletter gratuita del venerdì che riassume le storie chiave della settimana. Iscriviti e resta sul pezzo in pochi minuti a settimana:
Che cos'è Gemma 3 270M?
Gemma 3 270M è un modello foundation compatto, progettato per offrire ottimo instruction-following e generazione di testo strutturato, il tutto con soli 270 milioni di parametri. È il membro più piccolo della famiglia Gemma 3, ma eredita la stessa architettura e le stesse ricette di training delle varianti più grandi come Gemma 3 1B e 4B.
A differenza dei grandi modelli generalisti, Gemma 3 270M punta su efficienza e specializzazione. Ecco alcuni aspetti chiave del modello:
Gira senza sforzo sui dispositivi mobili; l'inferenza quantizzata (INT4, Q8 e altro) consuma meno dell'1% di batteria su più conversazioni.
Ha 100M di parametri del transformer e una grande matrice di embedding da 170M, che supporta un vocabolario di 256.000 token, ideale per gestire token rari in applicazioni di dominio specifico.
Sono disponibili anche checkpoint instruction-tuned, che facilitano la creazione di assistant, estrattori di dati e strumenti di scrittura creativa senza necessità di connettività cloud.
Gemma 3 270M ha buone capacità di seguire le istruzioni: il benchmark IFEval testa la capacità di un modello di seguire informazioni verificabili. Fonte: Google
Con una finestra di contesto da 32K, basso uso di memoria e buona accuratezza in compiti come estrazione di entità, classificazione e controlli leggeri di conformità, Gemma 3 270M è perfetto per gli sviluppatori di edge AI che cercano privacy, velocità ed efficienza dei costi.
Come accedere a Gemma 3 270M?
Ci sono diversi modi semplici per iniziare con Gemma 3 270M, a seconda della piattaforma che preferisci:
1. Cloud Run
Puoi effettuare il deployment di Gemma 3 su Google Cloud Run usando un container preconfigurato con accelerazione GPU per un'inferenza scalabile. Per Gemma 3 270M devi costruire il tuo container dal repo GitHub Gemma-on-CloudRun, perché solo le varianti più grandi (1B+) sono precompilate. Segui le istruzioni nella guida ufficiale di Google Cloud Run per abilitare un'inferenza veloce, sicura ed economica per app web o backend.
2. Android (Consigliato)
La migliore esperienza utente è on-device, usando una versione quantizzata di Gemma 3 270M (gemma3-270m-it-q8) e la Gemma Gallery App ufficiale di Google. Questo approccio offre inferenza offline estremamente rapida, grande privacy e consumo minimo di batteria.
3. Inferenza locale
Puoi anche eseguire Gemma 3 270M sul tuo laptop usando strumenti come:
LM Studio: Scarica un modello con estensione .gguf o .litertlm nel tuo ambiente LM Studio ed eseguilo subito nella sua interfaccia chat o ospitalo in locale.
Ollama: Puoi anche usare il comando ollama run gemma3:270m per eseguire questo modello localmente sulla tua macchina.
Llama.cpp: Fornisce anche il modello Gemma 3 270M per inferenza ultraleggera su CPU.
Docker: Puoi eseguire Gemma 3 anche tramite container Docker, sia da Hugging Face sia dal repository GitHub ufficiale di Gemma.
HuggingFace: Puoi accedere o effettuare inferenza su questo modello direttamente dall'interfaccia HuggingFace oppure scaricare modelli pretrained e instruction-tuned per l'uso locale.
4. Effettua il fine-tuning del modello
Gemma 3 270M è disponibile anche in Hugging Face Transformers, JAX o Unsloth per effettuare fine-tuning sui tuoi dati di dominio. Le dimensioni ridotte del modello lo rendono ideale per esperimenti rapidi e a basso costo, e i checkpoint QAT abilitano il deployment diretto in INT4 dopo il fine-tuning.
A seguire, ti guido nella creazione di un'app Android funzionante che usa il modello Gemma 3 270M per processare prompt testuali. Useremo il repository Gallery ufficiale di Google come base e lo personalizzeremo.
Passo 1: Clona e configura l'app
Iniziamo impostando un nuovo progetto e clonando il repository originale di Google.
Passo 1.1: Crea un nuovo progetto Android Studio
Sul tuo laptop, apri il progetto come nuovo progetto in Android Studio e seleziona Empty Activity.
Passo 1.2: Imposta una nuova activity vuota
Ora compila il nome della tua activity (per esempio, “Gemma 3 270M”) e lascia il resto invariato. Poi clicca su Finish.
Passo 1.3: Clona il repository principale
Apri il terminale dentro Android Studio (angolo in basso a sinistra) ed esegui i seguenti comandi bash:
git clone https://github.com/google-ai-edge/gallery
cd gallery/androidOfferto da
Questo aprirà il tuo progetto. Puoi vedere tutti i file del progetto nella parte sinistra della scheda.
Passo 2: Collega il modello
Per collegare il modello Gemma 3 270M alla tua app Android, devi registrarlo come un nuovo task nel file Tasks.kt dell'app.
Vai nella cartella data/ e apri il file Tasks.kt. Lì definirai una nuova voce di task specifica per la variante quantizzata Q8 del modello gemma3-270m-it-q8, ottimizzata per bassa memoria e inferenza on-device. Questo formato è adatto ai dispositivi mobili con RAM limitata.
Poi registra questo nuovo task nell'app includendolo nella lista principale dei task:
/** All tasks */
val TASKS: List<Task> = listOf(
TASK_LLM_ASK_IMAGE,
TASK_LLM_CHAT,
TASK_LLM_PROMPT_LAB,
TASK_IMAGE_CLASSIFICATION,
TASK_TEXT_CLASSIFICATION,
TASK_IMAGE_GENERATION,
TASK_GEMMA_3_270M
)Offerto da
Ecco cosa fa, in sintesi, la nuova definizione del task LiteRT nel codice:
Definisce un nuovo task: la variabile TASK_GEMMA_3_270M registra Gemma 3 270M come opzione selezionabile nel file Tasks.kt dell'app, così da comparire nell'elenco dei task.
Collega il file .task: il codice punta al bundle gemma3-270m-it-q8.task ospitato su Hugging Face, che viene scaricato nell'app e usato a runtime.
Dettagli del modello: il file .task contiene i pesi quantizzati del modello, il tokenizer, i metadati e le configurazioni richieste da LiteRT per eseguirlo in modo efficiente.
Variante Q8: questa quantizzazione bilancia risparmio di memoria e qualità dell'output, rendendolo leggero per i telefoni ma ancora valido nel seguire istruzioni.
Metadati del task: aggiunge descrizione, URL della documentazione e link al codice sorgente, facilitando la comprensione e la manutenzione del task dentro la Gallery app.
Elenco dei task: assicura infine che il modello sia riconosciuto nella lista dei task elencandolo come TASK_GEMMA_3_270M.
Passo 3: Correzione della popolazione dei modelli
Per impostazione predefinita, la Gallery app popola i suoi elenchi di modelli da una allowlist JSON remota. Se la tua voce personalizzata per Gemma 3 270M non è inclusa lì, non apparirà nella UI, anche se hai aggiunto TASK_GEMMA_3_270M nel codice. Questo passaggio innesta il tuo modello 270M nel task LLM Prompt Lab dopo il caricamento della allowlist, assicurando che sia selezionabile ogni volta.
Apri modelmanager/ModelManagerViewModel.kt, trova la funzione loadModelAllowlist() e, (dopo il ciclo che converte i modelli in allowlist) aggiungi:
Log.d(TAG, "Adding TASK_GEMMA_3_270M models to LLM_PROMPT_LAB")
for (gemmaModel in TASK_GEMMA_3_270M.models) {
if (!TASK_LLM_PROMPT_LAB.models.any { it.name == gemmaModel.name }) {
Log.d(TAG, "Adding model ${gemmaModel.name} to LLM_PROMPT_LAB")
TASK_LLM_PROMPT_LAB.models.add(gemmaModel)
}Offerto da
Ecco perché questo codice è necessario:
Reset della allowlist: all'avvio l'app pulisce e ricostruisce TASK_LLM_PROMPT_LAB.models dalla allowlist del server. Il tuo modello locale verrebbe eliminato se non lo reinietti.
Idempotente e sicuro contro i duplicati: la guardia any { it.name == gemmaModel.name } evita duplicati quando la funzione loadModelAllowlist() viene eseguita di nuovo.
Compatibile offline: questo codice funziona sia che la allowlist venga caricata dalla rete sia dal fallback su disco. Il modello viene aggiunto in entrambi i casi.
Ecco la funzione completa:
fun loadModelAllowlist() {
_uiState.update {
uiState.value.copy(
loadingModelAllowlist = true, loadingModelAllowlistError = ""
)
}
viewModelScope.launch(Dispatchers.IO) {
try {
withTimeoutOrNull(30000L) { // 30 second timeout
// Load model allowlist json.
Log.d(TAG, "Loading model allowlist from internet...")
val data = getJsonResponse<ModelAllowlist>(url = MODEL_ALLOWLIST_URL)
var modelAllowlist: ModelAllowlist? = data?.jsonObj
if (modelAllowlist == null) {
Log.d(TAG, "Failed to load model allowlist from internet. Trying to load it from disk")
modelAllowlist = readModelAllowlistFromDisk()
} else {
Log.d(TAG, "Done: loading model allowlist from internet")
saveModelAllowlistToDisk(modelAllowlistContent = data?.textContent ?: "{}")
}
if (modelAllowlist == null) {
Log.e(TAG, "Failed to load model allowlist from both internet and disk")
_uiState.update {
uiState.value.copy(
loadingModelAllowlist = false,
loadingModelAllowlistError = "Failed to load model list"
)
}
return@withTimeoutOrNull
}
Log.d(TAG, "Allowlist: $modelAllowlist")
// Convert models in the allowlist.
TASK_LLM_CHAT.models.clear()
TASK_LLM_PROMPT_LAB.models.clear()
TASK_LLM_ASK_IMAGE.models.clear()
try {
for (allowedModel in modelAllowlist.models) {
if (allowedModel.disabled == true) {
continue
}
val model = allowedModel.toModel()
if (allowedModel.taskTypes.contains(TASK_LLM_CHAT.type.id)) {
TASK_LLM_CHAT.models.add(model)
}
if (allowedModel.taskTypes.contains(TASK_LLM_PROMPT_LAB.type.id)) {
TASK_LLM_PROMPT_LAB.models.add(model)
}
if (allowedModel.taskTypes.contains(TASK_LLM_ASK_IMAGE.type.id)) {
TASK_LLM_ASK_IMAGE.models.add(model)
}
}
// Add models from TASK_GEMMA_3_270M to LLM_PROMPT_LAB
Log.d(TAG, "Adding TASK_GEMMA_3_270M models to LLM_PROMPT_LAB")
for (gemmaModel in TASK_GEMMA_3_270M.models) {
if (!TASK_LLM_PROMPT_LAB.models.any { it.name == gemmaModel.name }) {
Log.d(TAG, "Adding model ${gemmaModel.name} to LLM_PROMPT_LAB")
TASK_LLM_PROMPT_LAB.models.add(gemmaModel)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error processing model allowlist", e)
}
// Pre-process all tasks.
try {
processTasks()
} catch (e: Exception) {
Log.e(TAG, "Error processing tasks", e)
}
// Update UI state.
val newUiState = createUiState()
_uiState.update {
newUiState.copy(
loadingModelAllowlist = false,
)
}
// Process pending downloads.
try {
processPendingDownloads()
} catch (e: Exception) {
Log.e(TAG, "Error processing pending downloads", e)
}
} ?: run {
// Timeout occurred
Log.e(TAG, "Model allowlist loading timed out")
_uiState.update {
uiState.value.copy(
loadingModelAllowlist = false,
loadingModelAllowlistError = "Model list loading timed out"
)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error in loadModelAllowlist", e)
_uiState.update {
uiState.value.copy(
loadingModelAllowlist = false,
loadingModelAllowlistError = "Failed to load model list: ${e.message}"
)
}
}
}
}Offerto da
Passo 4: Aggiorna la logica di navigazione
Per impostazione predefinita, l'app presume che il task attualmente scelto corrisponda al modello cliccato. Dopo aver iniettato Gemma 3 270M nel task Prompt Lab (Passo 3), questa assunzione può rompersi. Questo passaggio rende la navigazione consapevole del modello, così tap, deep link e stato ripristinato atterrano sempre sulla schermata corretta.
Apri navigation/GalleryNavGraph.kt e aggiorna la logica di controllo della navigazione del modello come segue:
Prima:
onModelClicked = { model ->
navigateToTaskScreen(
navController = navController, taskType = curPickedTask.type, model = model
)
}Offerto da
Dopo:
onModelClicked = { model ->
Log.d("GalleryNavGraph", "Model clicked: ${model.name}, current task: ${curPickedTask.type}")
val actualTask = when {
TASK_LLM_CHAT.models.any { it.name == model.name } -> TASK_LLM_CHAT
TASK_LLM_PROMPT_LAB.models.any { it.name == model.name } -> TASK_LLM_PROMPT_LAB
TASK_LLM_ASK_IMAGE.models.any { it.name == model.name } -> TASK_LLM_ASK_IMAGE
TASK_TEXT_CLASSIFICATION.models.any { it.name == model.name } -> TASK_TEXT_CLASSIFICATION
TASK_IMAGE_CLASSIFICATION.models.any { it.name == model.name } -> TASK_IMAGE_CLASSIFICATION
TASK_IMAGE_GENERATION.models.any { it.name == model.name } -> TASK_IMAGE_GENERATION
else -> curPickedTask
}
Log.d("GalleryNavGraph", "Navigating to task: ${actualTask.type} for model: ${model.name}")
navigateToTaskScreen(
navController = navController, taskType = actualTask.type, model = model
)
}Offerto da
Se un modello compare in (o è stato iniettato in) un task diverso da quello attualmente selezionato, questo codice garantisce che la schermata corrisponda alle capacità del modello.
Con deep link o stato ripristinato, l'app può provare ad aprire un modello non più disponibile. Senza una gestione adeguata, si otterrebbe una schermata vuota. Per evitarlo, aggiungiamo un controllo di sicurezza: se il modello manca, registriamo un avviso e reindirizziamo l'utente. Ecco come farlo:
// Added error handling for model not found:
if (model != null) {
Log.d("GalleryNavGraph", "Model found for LlmChat: ${model.name}")
modelManagerViewModel.selectModel(model)
LlmChatScreen(/* ... */)
} else {
Log.w("GalleryNavGraph", "Model not found for LlmChat, modelName: ${it.arguments?.getString("modelName")}")
// Handle case where model is not found
androidx.compose.runtime.LaunchedEffect(Unit) {
navController.navigateUp()
}
}Offerto da
Questo codice assicura che l'app resti funzionale. Invece di bloccarsi o mostrare una schermata vuota, torna indietro in sicurezza usando navController. Così, anche se un modello manca, il flusso utente prosegue senza compromettere l'esperienza complessiva.
Passo 5: Sicurezza e affidabilità
Eseguire un large language model su un dispositivo mobile è diverso dall'eseguirlo su un computer desktop. L'archiviazione può essere inaffidabile, i budget di memoria sono più stretti e i fallimenti di inizializzazione vanno gestiti per evitare crash. Per rendere Gemma 3 270M (Q8, ~304MB) stabile su Android, ho introdotto controlli aggiuntivi di sicurezza e strategie di failover.
Passo 5.1: Validazione file con tolleranza sulle dimensioni
Prima di caricare il modello, l'app esegue controlli di sanità sul file .task. Si assicura che il file esista, sia leggibile e sia vicino alla dimensione attesa (entro ±10%). Questa tolleranza tiene conto di piccole differenze dovute a compressione o packaging, pur intercettando download corrotti o parziali.
// File validation and logging
Log.d(TAG, "Checking model file: $modelPath")
Log.d(TAG, "Model file exists: ${modelFile.exists()}")
Log.d(TAG, "Model file can read: ${modelFile.canRead()}")
Log.d(TAG, "Model file size: ${modelFile.length()} bytes")
Log.d(TAG, "Model file absolute path: ${modelFile.absolutePath}")
// Size validation with tolerance (10%)
val expectedSize = model.sizeInBytes
val fileSize = modelFile.length()
val sizeDifference = kotlin.math.abs(fileSize - expectedSize)
val sizeTolerance = (expectedSize * 0.1).toLong()
if (!modelFile.exists() || !modelFile.canRead() || sizeDifference > sizeTolerance) {
onDone("Model file invalid (missing/unreadable/wrong size).")
return
}Offerto da
Questo snippet valida un file di modello prima del caricamento. Registra dettagli di base come percorso, leggibilità e dimensione, poi verifica che il file esista, sia leggibile e corrisponda alla dimensione attesa. Se uno di questi controlli fallisce, la funzione si interrompe anticipatamente tramite il metodo onDone() invece di tentare l'inferenza rischiando un crash.
Passo 5.2: Selezione del backend consapevole della memoria
I modelli grandi possono generare errori Out-Of-Memory (OOM) se parti in modo aggressivo (output lunghi, picchi di allocazione GPU). Per mitigarli, ho aggiunto una logica che sceglie dinamicamente il backend più sicuro (CPU vs GPU/NPU) in base alla dimensione del file e alla configurazione del dispositivo, riducendo anche i token massimi per binari sovradimensionati.
val preferredBackend = when {
fileSize > 200_000_000L -> {
Log.d(TAG, "Model is large (${fileSize / 1024 / 1024}MB), using CPU backend for safety")
LlmInference.Backend.CPU
}
accelerator == Accelerator.CPU.label -> LlmInference.Backend.CPU
accelerator == Accelerator.GPU.label -> LlmInference.Backend.GPU
else -> LlmInference.Backend.CPU
}
val baseMaxTokens = 1024 // your default; tune to taste
val maxTokens = if (fileSize > 200_000_000L) {
kotlin.math.min(baseMaxTokens, 512)
} else {
baseMaxTokens
}Offerto da
Questo fa sì che i modelli grandi predefiniscano la CPU (più sicura ma più lenta). Il codice controlla la dimensione in byte del file .task (fileSize) e la confronta con una soglia (maxTokens). Se supera 200 MB, passa l'inferenza su CPU e dimezza il budget di token. Protegge da picchi di memoria (da allocazioni GPU simultanee) e richieste di generazione troppo estese.
Passo 5.3: Gestione delle eccezioni
Quando si inizializza LiteRT, è importante distinguere tra creazione del runtime e creazione della sessione. Se la creazione della sessione fallisce ma lasci il runtime aperto, puoi perdere risorse e bloccare memoria, causando errori a cascata. Per risolvere, ho inserito il codice in blocchi try/catch con cleanup esplicito.
val llmInference = try {
LlmInference.createFromOptions(context, options)
} catch (e: Exception) {
Log.e(TAG, "Failed to create LlmInference instance", e)
onDone("Failed to create model instance: ${e.message}")
return
}
val session = try {
LlmInferenceSession.createFromOptions(/* ... */)
} catch (e: Exception) {
Log.e(TAG, "Failed to create LlmInferenceSession", e)
try {
llmInference.close()
} catch (closeException: Exception) {
Log.e(TAG, "Failed to close LlmInference after session creation failure", closeException)
}
onDone("Failed to create model session: ${e.message}")
return
}Offerto da
Questo try/catch garantisce un rollback pulito: se fallisce la creazione del runtime, non si procede oltre; se fallisce la creazione della sessione, il runtime viene chiuso esplicitamente. Assicura che l'oggetto llmInference allocato sull'heap venga dismesso tramite .close(), liberando memoria e handle di file. Senza ciò, rischi riferimenti JNI che causano crash al successivo caricamento del modello.
Passo 5.4: Protezioni a tempo di inferenza
Anche se un modello si carica correttamente, l'inferenza può fallire per race condition, riferimenti nulli o istanze non inizializzate. Per rendere robusto questo passaggio, ho aggiunto controlli di null e catch di eccezioni attorno all'intero blocco di inferenza.
try {
Log.d(TAG, "Starting inference for model '${model.name}' with input: '${input.take(50)}...'")
val instance = model.instance as LlmModelInstance? ?: run {
Log.e(TAG, "Model instance is null for '${model.name}'")
resultListener("Error: Model not initialized", true)
return
}
} catch (e: Exception) {
Log.e(TAG, "Error during inference for model '${model.name}'", e)
resultListener("Error during inference: ${e.message}", true)
}Offerto da
Il codice sopra garantisce che l'inferenza prosegua solo con un'istanza di modello valida e intercetta pulitamente gli errori a runtime. Il cast as LlmModelInstance? verifica che l'istanza del modello sia tipizzata correttamente e il fallback in stile Elvis interrompe l'esecuzione se null. Wrappare il blocco in try/catch assicura che eventuali eccezioni siano catturate e restituite come messaggi di errore visibili all'utente.
Passo 6: Installa ed esegui l'app sul tuo dispositivo
Dopo aver effettuato tutte le modifiche necessarie, eseguiremo l'app. Per eseguire l'app sul tuo dispositivo Android, abbinalo ad Android Studio tramite il debug wireless seguendo i passaggi seguenti.
Passo 6.1: Configura il dispositivo Android
Impostazioni sul tuo dispositivo Android e abilita le Opzioni sviluppatore. Quindi attiva il Debug wireless. Dovresti vedere una schermata con due opzioni di pairing.
Passo 6.2: Gestisci i dispositivi in Android Studio
In Android Studio, apri il Device Manager (l'icona sembra un telefono con logo Android) e clicca su Abbina tramite codice QR.
Scansiona il codice QR con il tuo dispositivo e completa la procedura di pairing. Una volta connesso, vedrai il nome del dispositivo nel Device Manager. Assicurati che sistema e dispositivo Android siano collegati alla stessa rete Wi-Fi.
Passo 6.3: Esegui l'app
Avvia il mirroring del tuo dispositivo Android cliccando l'icona Device Mirroring (che raffigura uno schermo desktop e uno schermo del telefono). Una volta visto lo schermo del dispositivo in mirroring, clicca l'icona verde Run per distribuire l'app sul tuo dispositivo.
Passo 6.4: Scarica i modelli sul dispositivo
L'app si avvierà e si aprirà una finestra del browser che ti inviterà ad accedere a Hugging Face usando un token di tipo write per scaricare in sicurezza il file del modello.
Dopo aver effettuato l'accesso a HuggingFace, clicca su start download sul modello scelto e il download inizierà. Per questo esempio, uso di default il modello gemma3-270m-it-q8. Ora puoi iniziare a sperimentare con questo modello.
Passo 7: Inizia a sperimentare
Per questo tutorial, ho eseguito quattro esperimenti:
Esperimento 1: Scrittura libera
In questo esperimento ho chiesto al modello di scrivere una storia in forma libera, ecco i risultati:
Il modello produce testo in-topic con una struttura chiara, anche se tende a essere prudente e un po' generico. Su output lunghi, spesso ripete alcune frasi. È quindi più adatto per post brevi, descrizioni e scalette. La generazione del primo token è piuttosto veloce, circa 0,53 secondi, e la velocità di decoding è buona per l'inferenza on-device.
Esperimento 2: Riscrivere il tono
Poi ho usato un paragrafo pre-scritto e ho chiesto a Gemma 3 270M di riscriverlo con un tono amichevole.
La capacità di seguire le istruzioni è solida, con cambiamenti chiari tra toni formale, informale e amichevole mantenendo il significato. La velocità è quasi istantanea per brani brevi, il che lo rende adatto a rifinire email o convertire testi tra stili.
Esperimento 3: Riassumere un testo
Ho anche riassunto il testo pre-scritto in punti elenco. Puoi sperimentare con riassunti, punti chiave, takeaway e altro.
In questo esperimento, il modello riassume e coglie i punti chiave. La velocità è eccellente per documenti brevi e medi. Alcuni casi d'uso possono essere executive summary, highlight puntati o TL;DR.
Esperimento 4: Generatore di snippet di codice
Infine ho chiesto al modello di scrivere un piccolo snippet di codice. Ecco cosa ho riscontrato:
Il modello è efficace nel generare boilerplate e piccole utility, anche se può mancare di import, edge case o logica complessa senza guida. Gli snippet brevi sono generati rapidamente, mentre i completamenti lunghi rallentano leggermente man mano che cresce il contesto. Funziona al meglio per funzioni helper e configurazioni.
In questa guida abbiamo visto, passo dopo passo, come eseguire il modello Gemma 3 270M: dal download e collegamento del modello a un'app Android personalizzata, alla gestione della navigazione, dell'affidabilità e della sicurezza a tempo di inferenza.
Gemma 3 offre inferenza veloce con basso overhead computazionale, consuma pochissima energia, supporta la quantizzazione INT4 con impatto minimo sulle prestazioni e gira completamente on-device per mantenere la privacy.
Sono una Google Developers Expert in ML (Gen AI), una Kaggle 3x Expert e una Women Techmakers Ambassador con oltre 3 anni di esperienza nel tech. Ho co-fondato una startup health-tech nel 2020 e sto conseguendo un master in informatica al Georgia Tech, con specializzazione in machine learning.
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.
Google ha rilasciato di recente Gemma 3 270M, un modello ultracompatto da 270 milioni di parametri che rappresenta un nuovo paradigma per il deployment efficiente dell'IA. A differenza dei modelli generali di grandi dimensioni, Gemma 3 270M è pensato per il fine-tuning su compiti specifici mantenendo, già out-of-the-box, la capacità di seguire istruzioni.
In questo tutorial ti mostro come iniziare con Gemma 3 270M e perché è un'ottima scelta per gli sviluppatori che cercano:
Teniamo i nostri lettori aggiornati sulle ultime novità dell'IA con The Median, la nostra newsletter gratuita del venerdì che riassume le storie chiave della settimana. Iscriviti e resta sul pezzo in pochi minuti a settimana:
Che cos'è Gemma 3 270M?
Gemma 3 270M è un modello foundation compatto, progettato per offrire ottimo instruction-following e generazione di testo strutturato, il tutto con soli 270 milioni di parametri. È il membro più piccolo della famiglia Gemma 3, ma eredita la stessa architettura e le stesse ricette di training delle varianti più grandi come Gemma 3 1B e 4B.
A differenza dei grandi modelli generalisti, Gemma 3 270M punta su efficienza e specializzazione. Ecco alcuni aspetti chiave del modello:
Gemma 3 270M ha buone capacità di seguire le istruzioni: il benchmark IFEval testa la capacità di un modello di seguire informazioni verificabili. Fonte: Google
Con una finestra di contesto da 32K, basso uso di memoria e buona accuratezza in compiti come estrazione di entità, classificazione e controlli leggeri di conformità, Gemma 3 270M è perfetto per gli sviluppatori di edge AI che cercano privacy, velocità ed efficienza dei costi.
Come accedere a Gemma 3 270M?
Ci sono diversi modi semplici per iniziare con Gemma 3 270M, a seconda della piattaforma che preferisci:
1. Cloud Run
Puoi effettuare il deployment di Gemma 3 su Google Cloud Run usando un container preconfigurato con accelerazione GPU per un'inferenza scalabile. Per Gemma 3 270M devi costruire il tuo container dal repo GitHub Gemma-on-CloudRun, perché solo le varianti più grandi (1B+) sono precompilate. Segui le istruzioni nella guida ufficiale di Google Cloud Run per abilitare un'inferenza veloce, sicura ed economica per app web o backend.
2. Android (Consigliato)
La migliore esperienza utente è on-device, usando una versione quantizzata di Gemma 3 270M (gemma3-270m-it-q8) e la Gemma Gallery App ufficiale di Google. Questo approccio offre inferenza offline estremamente rapida, grande privacy e consumo minimo di batteria.
3. Inferenza locale
Puoi anche eseguire Gemma 3 270M sul tuo laptop usando strumenti come:
.ggufo.litertlmnel tuo ambiente LM Studio ed eseguilo subito nella sua interfaccia chat o ospitalo in locale.ollama run gemma3:270mper eseguire questo modello localmente sulla tua macchina.4. Effettua il fine-tuning del modello
Gemma 3 270M è disponibile anche in Hugging Face Transformers, JAX o Unsloth per effettuare fine-tuning sui tuoi dati di dominio. Le dimensioni ridotte del modello lo rendono ideale per esperimenti rapidi e a basso costo, e i checkpoint QAT abilitano il deployment diretto in INT4 dopo il fine-tuning.
A seguire, ti guido nella creazione di un'app Android funzionante che usa il modello Gemma 3 270M per processare prompt testuali. Useremo il repository Gallery ufficiale di Google come base e lo personalizzeremo.
Passo 1: Clona e configura l'app
Iniziamo impostando un nuovo progetto e clonando il repository originale di Google.
Passo 1.1: Crea un nuovo progetto Android Studio
Sul tuo laptop, apri il progetto come nuovo progetto in Android Studio e seleziona Empty Activity.
Passo 1.2: Imposta una nuova activity vuota
Ora compila il nome della tua activity (per esempio, “Gemma 3 270M”) e lascia il resto invariato. Poi clicca su Finish.
Passo 1.3: Clona il repository principale
Apri il terminale dentro Android Studio (angolo in basso a sinistra) ed esegui i seguenti comandi bash:
Questo aprirà il tuo progetto. Puoi vedere tutti i file del progetto nella parte sinistra della scheda.
Passo 2: Collega il modello
Per collegare il modello Gemma 3 270M alla tua app Android, devi registrarlo come un nuovo task nel file
Tasks.ktdell'app.Vai nella cartella
data/e apri il fileTasks.kt. Lì definirai una nuova voce di task specifica per la variante quantizzata Q8 del modellogemma3-270m-it-q8, ottimizzata per bassa memoria e inferenza on-device. Questo formato è adatto ai dispositivi mobili con RAM limitata.Nota: per questo tutorial userò una configurazione Q8, che richiede meno memoria. Puoi accedere a questo modello qui: https://huggingface.co/litert-community/gemma-3-270m-it
Poi registra questo nuovo task nell'app includendolo nella lista principale dei task:
Ecco cosa fa, in sintesi, la nuova definizione del task LiteRT nel codice:
TASK_GEMMA_3_270Mregistra Gemma 3 270M come opzione selezionabile nel fileTasks.ktdell'app, così da comparire nell'elenco dei task..task: il codice punta al bundlegemma3-270m-it-q8.taskospitato su Hugging Face, che viene scaricato nell'app e usato a runtime..taskcontiene i pesi quantizzati del modello, il tokenizer, i metadati e le configurazioni richieste da LiteRT per eseguirlo in modo efficiente.TASK_GEMMA_3_270M.Passo 3: Correzione della popolazione dei modelli
Per impostazione predefinita, la Gallery app popola i suoi elenchi di modelli da una allowlist JSON remota. Se la tua voce personalizzata per Gemma 3 270M non è inclusa lì, non apparirà nella UI, anche se hai aggiunto
TASK_GEMMA_3_270Mnel codice. Questo passaggio innesta il tuo modello 270M nel task LLM Prompt Lab dopo il caricamento della allowlist, assicurando che sia selezionabile ogni volta.Apri
modelmanager/ModelManagerViewModel.kt, trova la funzioneloadModelAllowlist()e, (dopo il ciclo che converte i modelli in allowlist) aggiungi:Ecco perché questo codice è necessario:
TASK_LLM_PROMPT_LAB.modelsdalla allowlist del server. Il tuo modello locale verrebbe eliminato se non lo reinietti.any { it.name == gemmaModel.name }evita duplicati quando la funzioneloadModelAllowlist()viene eseguita di nuovo.Ecco la funzione completa:
Passo 4: Aggiorna la logica di navigazione
Per impostazione predefinita, l'app presume che il task attualmente scelto corrisponda al modello cliccato. Dopo aver iniettato Gemma 3 270M nel task Prompt Lab (Passo 3), questa assunzione può rompersi. Questo passaggio rende la navigazione consapevole del modello, così tap, deep link e stato ripristinato atterrano sempre sulla schermata corretta.
Apri
navigation/GalleryNavGraph.kt e aggiorna la logica di controllo della navigazione del modello come segue:Prima:
Dopo:
Se un modello compare in (o è stato iniettato in) un task diverso da quello attualmente selezionato, questo codice garantisce che la schermata corrisponda alle capacità del modello.
Con deep link o stato ripristinato, l'app può provare ad aprire un modello non più disponibile. Senza una gestione adeguata, si otterrebbe una schermata vuota. Per evitarlo, aggiungiamo un controllo di sicurezza: se il modello manca, registriamo un avviso e reindirizziamo l'utente. Ecco come farlo:
Questo codice assicura che l'app resti funzionale. Invece di bloccarsi o mostrare una schermata vuota, torna indietro in sicurezza usando
navController. Così, anche se un modello manca, il flusso utente prosegue senza compromettere l'esperienza complessiva.Passo 5: Sicurezza e affidabilità
Eseguire un large language model su un dispositivo mobile è diverso dall'eseguirlo su un computer desktop. L'archiviazione può essere inaffidabile, i budget di memoria sono più stretti e i fallimenti di inizializzazione vanno gestiti per evitare crash. Per rendere Gemma 3 270M (Q8, ~304MB) stabile su Android, ho introdotto controlli aggiuntivi di sicurezza e strategie di failover.
Passo 5.1: Validazione file con tolleranza sulle dimensioni
Prima di caricare il modello, l'app esegue controlli di sanità sul file
.task. Si assicura che il file esista, sia leggibile e sia vicino alla dimensione attesa (entro ±10%). Questa tolleranza tiene conto di piccole differenze dovute a compressione o packaging, pur intercettando download corrotti o parziali.Questo snippet valida un file di modello prima del caricamento. Registra dettagli di base come percorso, leggibilità e dimensione, poi verifica che il file esista, sia leggibile e corrisponda alla dimensione attesa. Se uno di questi controlli fallisce, la funzione si interrompe anticipatamente tramite il metodo
onDone()invece di tentare l'inferenza rischiando un crash.Passo 5.2: Selezione del backend consapevole della memoria
I modelli grandi possono generare errori Out-Of-Memory (OOM) se parti in modo aggressivo (output lunghi, picchi di allocazione GPU). Per mitigarli, ho aggiunto una logica che sceglie dinamicamente il backend più sicuro (CPU vs GPU/NPU) in base alla dimensione del file e alla configurazione del dispositivo, riducendo anche i token massimi per binari sovradimensionati.
Questo fa sì che i modelli grandi predefiniscano la CPU (più sicura ma più lenta). Il codice controlla la dimensione in byte del file
.task(fileSize) e la confronta con una soglia (maxTokens). Se supera 200 MB, passa l'inferenza su CPU e dimezza il budget di token. Protegge da picchi di memoria (da allocazioni GPU simultanee) e richieste di generazione troppo estese.Passo 5.3: Gestione delle eccezioni
Quando si inizializza LiteRT, è importante distinguere tra creazione del runtime e creazione della sessione. Se la creazione della sessione fallisce ma lasci il runtime aperto, puoi perdere risorse e bloccare memoria, causando errori a cascata. Per risolvere, ho inserito il codice in blocchi try/catch con cleanup esplicito.
Questo try/catch garantisce un rollback pulito: se fallisce la creazione del runtime, non si procede oltre; se fallisce la creazione della sessione, il runtime viene chiuso esplicitamente. Assicura che l'oggetto
llmInferenceallocato sull'heap venga dismesso tramite.close(), liberando memoria e handle di file. Senza ciò, rischi riferimenti JNI che causano crash al successivo caricamento del modello.Passo 5.4: Protezioni a tempo di inferenza
Anche se un modello si carica correttamente, l'inferenza può fallire per race condition, riferimenti nulli o istanze non inizializzate. Per rendere robusto questo passaggio, ho aggiunto controlli di null e catch di eccezioni attorno all'intero blocco di inferenza.
Il codice sopra garantisce che l'inferenza prosegua solo con un'istanza di modello valida e intercetta pulitamente gli errori a runtime. Il cast
as LlmModelInstance?verifica che l'istanza del modello sia tipizzata correttamente e il fallback in stile Elvis interrompe l'esecuzione se null. Wrappare il blocco in try/catch assicura che eventuali eccezioni siano catturate e restituite come messaggi di errore visibili all'utente.Passo 6: Installa ed esegui l'app sul tuo dispositivo
Dopo aver effettuato tutte le modifiche necessarie, eseguiremo l'app. Per eseguire l'app sul tuo dispositivo Android, abbinalo ad Android Studio tramite il debug wireless seguendo i passaggi seguenti.
Passo 6.1: Configura il dispositivo Android
Impostazioni sul tuo dispositivo Android e abilita le Opzioni sviluppatore. Quindi attiva il Debug wireless. Dovresti vedere una schermata con due opzioni di pairing.
Passo 6.2: Gestisci i dispositivi in Android Studio
In Android Studio, apri il Device Manager (l'icona sembra un telefono con logo Android) e clicca su Abbina tramite codice QR.
Scansiona il codice QR con il tuo dispositivo e completa la procedura di pairing. Una volta connesso, vedrai il nome del dispositivo nel Device Manager. Assicurati che sistema e dispositivo Android siano collegati alla stessa rete Wi-Fi.
Passo 6.3: Esegui l'app
Avvia il mirroring del tuo dispositivo Android cliccando l'icona Device Mirroring (che raffigura uno schermo desktop e uno schermo del telefono). Una volta visto lo schermo del dispositivo in mirroring, clicca l'icona verde Run per distribuire l'app sul tuo dispositivo.
Passo 6.4: Scarica i modelli sul dispositivo
L'app si avvierà e si aprirà una finestra del browser che ti inviterà ad accedere a Hugging Face usando un token di tipo
writeper scaricare in sicurezza il file del modello.Dopo aver effettuato l'accesso a HuggingFace, clicca su start download sul modello scelto e il download inizierà. Per questo esempio, uso di default il modello
gemma3-270m-it-q8. Ora puoi iniziare a sperimentare con questo modello.Passo 7: Inizia a sperimentare
Per questo tutorial, ho eseguito quattro esperimenti:
Esperimento 1: Scrittura libera
In questo esperimento ho chiesto al modello di scrivere una storia in forma libera, ecco i risultati:
Il modello produce testo in-topic con una struttura chiara, anche se tende a essere prudente e un po' generico. Su output lunghi, spesso ripete alcune frasi. È quindi più adatto per post brevi, descrizioni e scalette. La generazione del primo token è piuttosto veloce, circa 0,53 secondi, e la velocità di decoding è buona per l'inferenza on-device.
Esperimento 2: Riscrivere il tono
Poi ho usato un paragrafo pre-scritto e ho chiesto a Gemma 3 270M di riscriverlo con un tono amichevole.
La capacità di seguire le istruzioni è solida, con cambiamenti chiari tra toni formale, informale e amichevole mantenendo il significato. La velocità è quasi istantanea per brani brevi, il che lo rende adatto a rifinire email o convertire testi tra stili.
Esperimento 3: Riassumere un testo
Ho anche riassunto il testo pre-scritto in punti elenco. Puoi sperimentare con riassunti, punti chiave, takeaway e altro.
In questo esperimento, il modello riassume e coglie i punti chiave. La velocità è eccellente per documenti brevi e medi. Alcuni casi d'uso possono essere executive summary, highlight puntati o TL;DR.
Esperimento 4: Generatore di snippet di codice
Infine ho chiesto al modello di scrivere un piccolo snippet di codice. Ecco cosa ho riscontrato:
Il modello è efficace nel generare boilerplate e piccole utility, anche se può mancare di import, edge case o logica complessa senza guida. Gli snippet brevi sono generati rapidamente, mentre i completamenti lunghi rallentano leggermente man mano che cresce il contesto. Funziona al meglio per funzioni helper e configurazioni.
Ho predisposto questo repository GitHub se vuoi esplorare il codice completo del progetto.
Conclusione
In questa guida abbiamo visto, passo dopo passo, come eseguire il modello Gemma 3 270M: dal download e collegamento del modello a un'app Android personalizzata, alla gestione della navigazione, dell'affidabilità e della sicurezza a tempo di inferenza.
Gemma 3 offre inferenza veloce con basso overhead computazionale, consuma pochissima energia, supporta la quantizzazione INT4 con impatto minimo sulle prestazioni e gira completamente on-device per mantenere la privacy.