Ga naar hoofdinhoud

Gemma 3 270M: een gids met demoproject (Android-app)

Leer hoe je het Gemma 3 270M-model instelt, integreert en draait in een Android-applicatie, plus best practices voor betrouwbaarheid en veiligheid bij on-device inference.
· 12 min lezen

Verken met AI

Google bracht onlangs Gemma 3 270M uit, een ultracompact model met 270 miljoen parameters dat een nieuw paradigma vertegenwoordigt in efficiënte AI-implementatie. In tegenstelling tot enorme generalistische modellen is Gemma 3 270M ontworpen voor taakspecifieke fine-tuning, terwijl het direct uit de doos instructies kan volgen.

In deze tutorial laat ik je zien hoe je aan de slag gaat met Gemma 3 270M en waarom het een uitstekende keuze is voor developers die behoefte hebben aan:

  • Bliksemsnelle inference met minimale rekenoverhead
  • Uiterste energiezuinigheid (verbruikt slechts 0,75% batterij voor 25 conversaties op Pixel 9 Pro)
  • Quantisatie met INT4-precisie en minimale prestatiedaling
  • Volledige privacy met volledige uitvoering op het apparaat

We houden onze lezers op de hoogte van het laatste AI-nieuws via The Median, onze gratis vrijdagse nieuwsbrief die de belangrijkste verhalen van de week uitlegt. Abonneer je en blijf scherp in slechts een paar minuten per week:

Wat is Gemma 3 270M?

Gemma 3 270M is een compact foundationmodel, ontworpen voor sterk instructievolgend gedrag en gestructureerde tekstgeneratie, en dat met slechts 270 miljoen parameters. Het is het kleinste lid van de Gemma 3-familie, maar het erft dezelfde architectuur en trainingsrecepten als grotere varianten zoals Gemma 3 1B en 4B.

In tegenstelling tot generalistische grote modellen focust Gemma 3 270M op efficiëntie en specialisatie. Enkele kernpunten over dit model:

  • Het draait moeiteloos op mobiele apparaten; gequantiseerde inference (INT4, Q8 en meer) verbruikt minder dan 1% batterij over meerdere gesprekken.
  • Het heeft 100M transformerparameters en een grote embeddingmatrix van 170M, die een vocabulaire van 256.000 tokens ondersteunt—ideaal voor zeldzame tokens in domeinspecifieke toepassingen.
  • Er zijn ook instruction-tuned checkpoints beschikbaar, waardoor je eenvoudig assistants, data-extractors en creatieve schrijftools kunt bouwen die geen cloudverbinding nodig hebben.

Gemma 3 270M performance on IFEval

Gemma 3 270M kan goed instructies volgen—de IFEval-benchmark test het vermogen van een model om verifieerbare informatie te volgen. Bron: Google

Met een contextvenster van 32K, laag geheugenverbruik en solide nauwkeurigheid bij taken als entiteitsextractie, classificatie en lichte compliancechecks is Gemma 3 270M perfect voor edge-AI-developers die privacy, snelheid en kostenefficiëntie zoeken.

Hoe krijg je toegang tot Gemma 3 270M?

Er zijn verschillende eenvoudige manieren om aan de slag te gaan met Gemma 3 270M, afhankelijk van je voorkeursplatform:

1. Cloud Run

Je kunt Gemma 3 implementeren op Google Cloud Run met een vooraf gebouwde GPU-versnelde container voor schaalbare inference. Voor Gemma 3 270M moet je je container bouwen vanuit de Gemma-on-CloudRun GitHub-repo, omdat alleen de grotere varianten (1B+) vooraf gebouwd zijn. Volg de instructies in de officiële handleiding van Google Cloud Run om snelle, veilige en kostenefficiënte inference voor web- of backendapplicaties te activeren.

2. Android (aanbevolen)

De beste gebruikerservaring is on-device, met een gequantiseerde versie van Gemma 3 270M (gemma3-270m-it-q8) en de officiële Gemma Gallery-app van Google. Deze aanpak levert extreem snelle offline inference, sterke privacy en minimaal batterijverbruik.

3. Lokale inference

Je kunt Gemma 3 270M ook op je laptop draaien met tools zoals:

  • LM Studio: Download een model met .gguf of .litertlm-extensie in je LM Studio-omgeving en draai het direct in de chatinterface of host het lokaal.
  • Ollama: Je kunt ook het commando ollama run gemma3:270m gebruiken om dit model lokaal op je machine te draaien.
  • Llama.cpp: Biedt ook het Gemma 3 270M-model voor ultralichte CPU-inference.
  • Docker: Je kunt Gemma 3 ook draaien via een Docker-container, afkomstig van Hugging Face of uit de officiële GitHub-repository van Gemma.
  • HuggingFace: Gebruikers kunnen dit model direct benaderen of inference draaien via de HuggingFace-interface, of zowel pretrained als instruction-tuned modellen downloaden voor lokaal gebruik.

4. Het model fine-tunen

Gemma 3 270M is ook beschikbaar in Hugging Face Transformers, JAX of Unsloth om te fine-tunen op je domeinspecifieke data. De kleine modelgrootte maakt het ideaal voor snelle, goedkope experimenten, en QAT-checkpoints maken directe INT4-implementatie na finetuning mogelijk.

Vervolgens neem ik je mee in het bouwen van een werkende Android-app die het Gemma 3 270M-model gebruikt om tekstprompts te verwerken. We gebruiken Google’s officiële Gallery-repository als basis en passen die aan.

Stap 1: Clone en stel de app in

Laten we beginnen met het opzetten van een nieuw project en het clonen van de oorspronkelijke repository van Google.

Stap 1.1: Maak een nieuw Android Studio-project

Open op je laptop het project als een nieuw project in Android Studio en kies een Empty Activity.

Android Studio New Project window for running gemma 3 270M

Stap 1.2: Stel een nieuwe lege activity in

Vul daarna de naam van je activity in (bijvoorbeeld “Gemma 3 270M”) en laat de rest ongewijzigd. Klik vervolgens op Finish.

Stap 1.3: Clone de hoofdrepository

Open nu de terminal in Android Studio (linkeronderhoek) en voer de volgende bash-commando’s uit:

git clone https://github.com/google-ai-edge/gallery
cd gallery/android

Android Studio Terminal for running gemma 3 270M

Hiermee wordt je project geopend. Je kunt alle projectbestanden bekijken aan de linkerkant van het tabblad.

Android Studio Files

Stap 2: Koppel het model

Om het Gemma 3 270M-model met je Android-app te verbinden, moet je het registreren als een nieuwe taak in het Tasks.kt-bestand van de app.

Navigeer naar de map data/ en open het bestand Tasks.kt. Daar definieer je een nieuwe taakentry specifiek voor de Q8-gequantiseerde variant van het model gemma3-270m-it-q8, die is geoptimaliseerd voor laag geheugen en on-device inference. Deze indeling is geschikt voor mobiele apparaten met beperkte RAM.

Tasks.kt file location

Let op: voor deze tutorial gebruik ik een Q8-modelconfiguratie, die minder geheugen vereist. Je vindt dit model hier: https://huggingface.co/litert-community/gemma-3-270m-it

val TASK_GEMMA_3_270M = Task(
  type = TaskType.LLM_PROMPT_LAB,
  icon = Icons.Outlined.Widgets,
  models = mutableListOf(
    Model(
      name = "gemma3-270m-it-q8",
      downloadFileName = "gemma3-270m-it-q8.task",
      url = "https://huggingface.co/litert-community/gemma-3-270m-it/resolve/main/gemma3-270m-it-q8.task",
      sizeInBytes = 318_767_104L
    )
  ),
  description = "Gemma 3 270M (Q8): On-device instruction-following LLM",
  docUrl = "https://ai.google.dev/gemma",
  sourceCodeUrl = "https://github.com/google-ai-edge/gallery",
  textInputPlaceHolderRes = R.string.chat_textinput_placeholder
)

Registreer deze nieuwe taak vervolgens in de app door hem op te nemen in de hoofdlijst met taken:

/** 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
)

Hier is een uitleg van wat de nieuwe LiteRT-taakdefinitie in de code doet:

  • Definieert een nieuwe taak: de variabele TASK_GEMMA_3_270M registreert Gemma 3 270M als een selecteerbare optie in het Tasks.kt-bestand van de app, zodat het in de takenlijst verschijnt.
  • Koppelt het .task-bestand: de bovenstaande code verwijst naar het gemma3-270m-it-q8.task-pakket op Hugging Face, dat binnen de app wordt gedownload en runtime wordt gebruikt.
  • Modeldetails: het .task-bestand bevat de gequantiseerde modelgewichten, tokenizer, metadata en configs die LiteRT nodig heeft om het model efficiënt te draaien.
  • Q8-variant: deze quantisatie balanceert geheugensparing met goede outputkwaliteit, waardoor het licht genoeg is voor telefoons en toch instructies goed volgt.
  • Taakmetadata: voegt beschrijving, documentatie-URL en broncodelink toe, zodat de taak in de Gallery-app makkelijker te begrijpen en te onderhouden is.
  • Takenlijst: zorgt er tot slot voor dat het model wordt herkend onder de takenlijst door de modeltaak op te nemen als TASK_GEMMA_3_270M.

Stap 3: Fix voor modelpopulatie

Standaard vult de Gallery-app zijn modellijsten vanuit een externe allowlist-JSON. Als je aangepaste Gemma 3 270M-entry daar niet in staat, verschijnt die niet in de UI, zelfs als je TASK_GEMMA_3_270M in de code hebt toegevoegd. Deze stap voegt je 270M-model toe aan de taak LLM Prompt Lab nadat de allowlist is geladen, zodat het elke keer selecteerbaar is.

Open modelmanager/ModelManagerViewModel.kt, zoek de functie loadModelAllowlist() en voeg (na de lus die allowlisted modellen converteert) toe:

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)
              }

Waarom deze code nodig is:

  • Allowlist-reset: de app wist en bouwt TASK_LLM_PROMPT_LAB.models opnieuw op vanuit de serverallowlist bij het opstarten. Je lokale model zou worden verwijderd tenzij je het opnieuw injecteert.
  • Idempotent en duplicaatveilig: de guard any { it.name == gemmaModel.name } voorkomt duplicaten wanneer de functie loadModelAllowlist() opnieuw draait.
  • Offline-vriendelijk: deze code werkt of de allowlist nu van het netwerk of van de fallback op schijf wordt geladen. Het model wordt in beide gevallen toegevoegd.

Hier is de volledige functie:

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}"
          ) 
        }
      }
    }
  }

Stap 4: Werk de navigatielogica bij

Standaard gaat de app ervan uit dat de huidige gekozen taak overeenkomt met het aangeklikte model. Zodra je Gemma 3 270M in de Prompt Lab-taak injecteert (Stap 3), kan die aanname breken. Deze stap maakt de navigatie modelbewust, zodat tikken, deeplinks en herstelde status altijd op het juiste scherm landen.

Open navigation/GalleryNavGraph.kt en werk de navigatiebesturing voor modellen als volgt bij:

Voor:

onModelClicked = { model ->
  navigateToTaskScreen(
    navController = navController, taskType = curPickedTask.type, model = model
  )
}

Na:

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
  )
}

Als een model voorkomt in (of is geïnjecteerd in) een andere taak dan de huidige selectie, garandeert deze code dat het scherm overeenkomt met de mogelijkheden van het model. 

Bij deeplinks of herstelde status kan de app proberen een model te openen dat niet meer beschikbaar is. Zonder goede afhandeling resulteert dit in een leeg scherm. Om dit te voorkomen, voegen we een safety check toe: als het model ontbreekt, loggen we een waarschuwing en sturen we de gebruiker terug. Zo doe je dat:

// 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()
  }
}

Deze code zorgt ervoor dat de app bruikbaar blijft. In plaats van vast te lopen of een leeg scherm te tonen, navigeert hij veilig terug met navController. Zo blijft de gebruikersflow soepel, ook als een model ontbreekt.

Stap 5: Veiligheid en betrouwbaarheid 

Een groot taalmodel op een mobiel apparaat draaien verschilt van desktopgebruik. Opslag kan onbetrouwbaar zijn, geheugentoewijzingen zijn krapper en initialisatiefouten moeten worden afgehandeld om crashes te voorkomen. Om Gemma 3 270M (Q8, ~304MB) stabiel op Android te maken, heb ik extra veiligheidschecks en failoverstrategieën toegevoegd.

Stap 5.1: Bestandvalidatie met tolerante grootte

Voor het laden van het model voert de app sanity checks uit op het .task-bestand. Dit zorgt ervoor dat het modembestand bestaat, leesbaar is en in de buurt komt van de verwachte grootte (binnen ±10%). Deze tolerantie houdt rekening met kleine verschillen door compressie of verpakking, terwijl corrupte of gedeeltelijke downloads toch worden gedetecteerd.

// 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
}

Deze snippet valideert een modembestand vóór het laden. Het logt basisdetails zoals pad, leesbaarheid en grootte, en controleert vervolgens of het bestand bestaat, leesbaar is en overeenkomt met de verwachte grootte. Als een van deze checks faalt, stopt de functie vroegtijdig via de methode onDone() in plaats van inference te proberen en een crash te riskeren.

Stap 5.2: Geheugenbewuste backendselectie 

Grote modellen kunnen Out-Of-Memory (OOM)-fouten geven als je agressief start (lange outputs, GPU-allocatiespikes). Om dit te beperken, heb ik logica toegevoegd om dynamisch de veiligste backend te kiezen (CPU vs GPU/NPU) op basis van bestandsgrootte en apparaatconfiguratie, en tegelijkertijd het maximale aantal tokens terug te schroeven voor te grote binaries.

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
}

Dit zorgt ervoor dat grote modellen standaard naar de CPU gaan (veiliger maar trager). De code controleert de bytesize van het .task-bestand (fileSize) en vergelijkt die met een drempel (maxTokens). Als die groter is dan 200 MB, schakelt hij over naar CPU-inference en halveert hij het tokenbudget. Dit beschermt tegen geheugenpieken (door gelijktijdige GPU-allocatie) en te grote generatieverzoeken.

Stap 5.3: Exceptieafhandeling

Bij het initialiseren van LiteRT is het belangrijk onderscheid te maken tussen runtimecreatie en sessiecreatie. Als sessiecreatie faalt maar je laat de runtime open, kun je resources lekken en geheugen vergrendelen, wat tot kettingfouten leidt. Om dit te verhelpen heb ik de code in try/catch-blokken verpakt met expliciete cleanup.

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
}

Deze try/catch garandeert een schone rollback: als runtimecreatie faalt, gaat er niets verder; als sessiecreatie faalt, wordt de runtime expliciet gesloten. Het zorgt ervoor dat het op de heap gealloceerde object llmInference via .close() wordt opgeruimd, waardoor geheugen en bestandshandles worden vrijgegeven. Zonder dit riskeer je JNI-referenties die bij de volgende modellading crashen.

Stap 5.4: Guards tijdens inference

Zelfs als een model succesvol laadt, kan inference falen door raceconditions, null-referenties of niet-geïnitialiseerde instanties. Om deze stap te hardenen heb ik null-checks en exception handling rond het volledige inferenceblok toegevoegd.

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)
}

De bovenstaande code garandeert dat inference alleen doorgaat met een geldige modelinstantie en vangt runtimefouten netjes af. De cast as LlmModelInstance? controleert of de modelinstantie correct getypt is en de Elvis-achtige fallback onderbreekt de uitvoering als die null is. Door het blok in try/catch te wikkelen, worden alle exceptions opgevangen en teruggekoppeld als voor de gebruiker zichtbare foutmeldingen.

Stap 6: Installeer en draai de app op je apparaat

Zodra je alle benodigde wijzigingen hebt doorgevoerd, gaan we de app draaien. Om je app op je lokale Android-apparaat te draaien, koppel je je Android-apparaat aan Android Studio met draadloos debuggen via de volgende stappen.

Stap 6.1: Stel het Android-apparaat in

Ga op je Android-apparaat naar de app Instellingen en schakel Ontwikkelaarsopties in. Zet daarna Draadloos debuggen aan. Je ziet een scherm met twee koppelopties.

Wireless debugging settings on Android device for running gemma 3 270M

Stap 6.2: Beheer apparaten in Android Studio

Open in Android Studio de Device Manager (het pictogram lijkt op een telefoon met een Android-logo) en klik op Pair using QR code.

Pair new device with WiFi

Scan de QR-code met je apparaat en rond de koppelingsstap af. Zodra je apparaat verbonden is, zie je de apparaatnaam onder Device Manager. Zorg dat je systeem en Android-apparaat met hetzelfde wifinetwerk verbonden zijn.

Device Manager

Stap 6.3: Draai de app

Start mirroring van je Android-apparaat door op het pictogram Device Mirroring te klikken (een desktopscherm en telefoonscherm). Zodra je je apparaatscherm gespiegeld ziet, klik je op het groene Run-pictogram om de app naar je apparaat te deployen.

Stap 6.4: Download de modellen op het apparaat

De app start en er opent een browservenster dat je vraagt in te loggen bij Hugging Face met een write-token om het modembestand veilig te downloaden.

Zodra je bent ingelogd bij HuggingFace, klik je op start download bij het gewenste model en start de download. Voor dit voorbeeld gebruik ik standaard het model gemma3-270m-it-q8. Nu kun je beginnen met experimenteren met dit model.

Stap 7: Ga experimenteren

Voor deze tutorial heb ik vier experimenten uitgevoerd:

Experiment 1: Vrij schrijven

In dit experiment vroeg ik het model om een vrij verhaal te schrijven, en dit zijn de resultaten:

Het model produceert tekst die on topic is en duidelijk gestructureerd, maar neigt naar veilig en enigszins generiek. Bij langere outputs herhaalt het model vaak bepaalde zinnen. Dit maakt het het meest geschikt voor korte posts, beschrijvingen en outlines. De eerste-token-generatie is echter behoorlijk snel met ongeveer 0,53 seconden, en de decodesnelheid is ook goed voor on-device inference.

Experiment 2: Toon herschrijven

Vervolgens gebruikte ik een vooraf geschreven alinea en vroeg ik Gemma 3 270M om de alinea in een vriendelijke toon te herschrijven.

Exp 2: Rewrite tone

Het volgen van instructies is sterk, met duidelijke verschuivingen tussen formele, informele en vriendelijke tonen, terwijl de betekenis behouden blijft. De snelheid is vrijwel direct voor korte passages, wat het zeer geschikt maakt voor het oppoetsen van e-mails of het converteren van tekst tussen stijlen.

Experiment 3: Tekst samenvatten

Ik heb de vooraf geschreven tekst ook samengevat in bulletpoints. Je kunt experimenteren met samenvattingen, punten, takeaways en meer.

Exp 3: Summarize text

In dit experiment vat het model samen en pikt het de kernpunten eruit. De snelheid is uitstekend voor korte en middellange documenten. Mogelijke use-cases zijn executive summaries, bullet-highlights of TL;DR’s. 

Experiment 4: Codefragment schrijven

Tot slot vroeg ik het model om een kleine code snippet te schrijven. Dit viel me op:

Exp 4: Code snippet writer

Het model is effectief in het genereren van boilerplate en kleine utilities, maar kan zonder sturing imports, randgevallen of complexe logica missen. Korte snippets worden snel gegenereerd, terwijl langere completions iets vertragen naarmate de context groeit. Het werkt het best voor helperfuncties en configs. 

Ik heb deze GitHub-repository opgezet als je de volledige projectcode wilt verkennen.

Conclusie

In deze gids zijn we stap voor stap door het draaien van het Gemma 3 270M-model gegaan, van downloaden en aansluiten in een aangepaste Android-app tot het afhandelen van navigatie, betrouwbaarheid en veiligheid tijdens inference.

Gemma 3 biedt snelle inference met weinig rekenoverhead, verbruikt zeer weinig energie, ondersteunt INT4-quantisatie met minimale impact op prestaties en draait volledig op het apparaat om privacy te waarborgen.

Aashi Dutt

Auteur

Aashi Dutt

Ik ben een Google Developers Expert in ML (Gen AI), een Kaggle 3x Expert en een Women Techmakers Ambassador met meer dan 3 jaar ervaring in tech. In 2020 heb ik een healthtech-startup mee opgericht en ik volg een master computer science aan Georgia Tech, met als specialisatie machine learning.

Onderwerpen

Gerelateerd