Come ospitare un LLM in un sidecar per una funzione Cloud Run

Come ospitare un modello LLM in un sidecar per una funzione Cloud Run

Informazioni su questo codelab

subjectUltimo aggiornamento: mar 27, 2025
account_circleScritto da un Googler

1. Introduzione

In questo codelab, imparerai a ospitare un modello gemma3:4b in un sidecar per una funzione Cloud Run. Quando un file viene caricato in un bucket Cloud Storage, viene attivata la funzione Cloud Run. La funzione invierà i contenuti del file a Gemma 3 nel sidecar per il riassunto.

  • Come eseguire l'inferenza utilizzando una funzione Cloud Run e un modello LLM ospitato in un sidecar con GPU
  • Come utilizzare la configurazione di uscita VPC diretto per una GPU Cloud Run per caricare e pubblicare il modello più velocemente
  • Come utilizzare genkit per interagire con il modello Ollama ospitato

2. Prima di iniziare

Per utilizzare la funzionalità GPU, devi richiedere un aumento della quota per una regione supportata. La quota necessaria è nvidia_l4_gpu_allocation_no_zonal_redundancy, che si trova nell'API Cloud Run Admin. Ecco il link diretto per richiedere la quota.

3. Configurazione e requisiti

Imposta le variabili di ambiente che verranno utilizzate durante questo codelab.

PROJECT_ID=<YOUR_PROJECT_ID>
REGION
=<YOUR_REGION>

AR_REPO
=codelab-crf-sidecar-gpu
FUNCTION_NAME
=crf-sidecar-gpu
BUCKET_GEMMA_NAME
=$PROJECT_ID-codelab-crf-sidecar-gpu-gemma3
BUCKET_DOCS_NAME
=$PROJECT_ID-codelab-crf-sidecar-gpu-docs
SERVICE_ACCOUNT
="crf-sidecar-gpu"
SERVICE_ACCOUNT_ADDRESS
=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
IMAGE_SIDECAR
=$REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/ollama-gemma3

Crea l'account di servizio eseguendo questo comando:

gcloud iam service-accounts create $SERVICE_ACCOUNT \
 
--display-name="SA for codelab crf sidecar with gpu"

Utilizzeremo lo stesso account di servizio utilizzato come identità della funzione Cloud Run come account di servizio per l'attivatore eventarc per richiamare la funzione Cloud Run. Se preferisci, puoi creare un'altra entità di amministratore per Eventarc.

gcloud projects add-iam-policy-binding $PROJECT_ID \
   
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
   
--role=roles/run.invoker

Concedi inoltre all'account di servizio l'accesso per ricevere gli eventi Eventarc.

gcloud projects add-iam-policy-binding $PROJECT_ID \
   
--member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
   
--role="roles/eventarc.eventReceiver"

Crea un bucket che ospiterà il modello ottimizzato. Questo codelab utilizza un bucket regionale. Puoi anche utilizzare un bucket multiregionale.

gsutil mb -l $REGION gs://$BUCKET_GEMMA_NAME

Quindi concedi all'amministratore delegato l'accesso al bucket.

gcloud storage buckets add-iam-policy-binding gs://$BUCKET_GEMMA_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin

Ora crea un bucket regionale in cui verranno archiviati i documenti che vuoi riepilogare. Puoi utilizzare anche un bucket multiregionale, a condizione che tu aggiorni di conseguenza l'attivatore Eventarc (mostrato alla fine di questo codelab).

gsutil mb -l $REGION gs://$BUCKET_DOCS_NAME

Quindi concedi all'amministratore delegato l'accesso al bucket Gemma 3.

gcloud storage buckets add-iam-policy-binding gs://$BUCKET_GEMMA_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin

e il bucket Documenti.

gcloud storage buckets add-iam-policy-binding gs://$BUCKET_DOCS_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin

Crea un repository Artifact Registry per l'immagine Ollama che verrà utilizzata nel sidecar

gcloud artifacts repositories create $AR_REPO \
   
--repository-format=docker \
   
--location=$REGION \
   
--description="codelab for CR function and gpu sidecar" \
   
--project=$PROJECT_ID

4. Scarica il modello Gemma 3

Per prima cosa, devi scaricare il modello Gemma 3 4b da ollama. Per farlo, installa ollama ed esegui il modello gemma3:4b localmente.

curl -fsSL https://ollama.com/install.sh | sh
ollama serve

Ora, in una finestra del terminale separata, esegui il seguente comando per scaricare il modello. Se utilizzi Cloud Shell, puoi aprire un'altra finestra del terminale facendo clic sull'icona Più nella barra dei menu in alto a destra.

ollama run gemma3:4b

Una volta avviato ollama, non esitare a porre alcune domande al modello, ad esempio

"why is the sky blue?"

Al termine della chat con ollama, puoi uscire eseguendo

/bye

Quindi, nella prima finestra del terminale, esegui il seguente comando per interrompere il servizio ollama in locale

# on Linux / Cloud Shell press Ctrl^C or equivalent for your shell

Qui puoi trovare dove Ollama scarica i modelli a seconda del sistema operativo.

https://github.com/ollama/ollama/blob/main/docs/faq.md#where-are-models-stored

Se utilizzi Cloud Workstations, puoi trovare i modelli ollama scaricati qui /home/$USER/.ollama/models

Verifica che i tuoi modelli siano ospitati qui:

ls /home/$USER/.ollama/models

Ora sposta il modello gemma3:4b nel tuo bucket GCS

gsutil cp -r /home/$USER/.ollama/models gs://$BUCKET_GEMMA_NAME

5. Crea la funzione Cloud Run

Crea una cartella principale per il codice sorgente.

mkdir codelab-crf-sidecar-gpu &&
cd codelab
-crf-sidecar-gpu &&
mkdir cr
-function &&
mkdir ollama
-gemma3 &&
cd cr
-function

Crea una sottocartella denominata src. All'interno della cartella, crea un file denominato index.ts

mkdir src &&
touch src
/index.ts

Aggiorna index.ts con il seguente codice:

//import util from 'util';
import { cloudEvent, CloudEvent } from "@google-cloud/functions-framework";
import { StorageObjectData } from "@google/events/cloud/storage/v1/StorageObjectData";
import { Storage } from "@google-cloud/storage";

// Initialize the Cloud Storage client
const storage = new Storage();

import { genkit } from 'genkit';
import { ollama } from 'genkitx-ollama';

const ai = genkit({
   
plugins: [
       
ollama({
           
models: [
               
{
                   
name: 'gemma3:4b',
                   
type: 'generate', // type: 'chat' | 'generate' | undefined
               
},
           
],
           
serverAddress: 'http://127.0.0.1:11434', // default local address
       
}),
   
],
});


// Register a CloudEvent callback with the Functions Framework that will
// be triggered by Cloud Storage.

//functions.cloudEvent('helloGCS', await cloudEvent => {
cloudEvent("gcs-cloudevent", async (cloudevent: CloudEvent<StorageObjectData>) => {
   
console.log("---------------\nProcessing for ", cloudevent.subject, "\n---------------");

   
if (cloudevent.data) {

       
const data = cloudevent.data;

       
if (data && data.bucket && data.name) {
           
const bucketName = cloudevent.data.bucket;
           
const fileName = cloudevent.data.name;
           
const filePath = `${cloudevent.data.bucket}/${cloudevent.data.name}`;

           
console.log(`Attempting to download: ${filePath}`);

           
try {
               
// Get a reference to the bucket
               
const bucket = storage.bucket(bucketName!);

               
// Get a reference to the file
               
const file = bucket.file(fileName!);

               
// Download the file's contents
               
const [content] = await file.download();

               
// 'content' is a Buffer. Convert it to a string.
               
const fileContent = content.toString('utf8');

               
console.log(`Sending file to Gemma 3 for summarization`);
               
const { text } = await ai.generate({
                   
model: 'ollama/gemma3:4b',
                   
prompt: `Summarize the following document in just a few sentences ${fileContent}`,
               
});

               
console.log(text);

           
} catch (error: any) {

               
console.error('An error occurred:', error.message);
           
}
       
} else {
           
console.warn("CloudEvent bucket name is missing!", cloudevent);
       
}
   
} else {
       
console.warn("CloudEvent data is missing!", cloudevent);
   
}
});

Ora, nella directory principale crf-sidecar-gpu, crea un file denominato package.json con i seguenti contenuti:

{
    "main": "lib/index.js",
    "name": "ingress-crf-genkit",
    "version": "1.0.0",
    "scripts": {
        "build": "tsc"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
        "@google-cloud/functions-framework": "^3.4.0",
        "@google-cloud/storage": "^7.0.0",
        "genkit": "^1.1.0",
        "genkitx-ollama": "^1.1.0",
        "@google/events": "^5.4.0"
    },
    "devDependencies": {
        "typescript": "^5.5.2"
    }
}

Crea un tsconfig.json anche a livello di directory principale con i seguenti contenuti:

{
 
"compileOnSave": true,
 
"include": [
   
"src"
 
],
 
"compilerOptions": {
   
"module": "commonjs",
   
"noImplicitReturns": true,
   
"outDir": "lib",
   
"sourceMap": true,
   
"strict": true,
   
"target": "es2017",
   
"skipLibCheck": true,
   
"esModuleInterop": true
 
}
}

6. esegui il deployment della funzione

In questo passaggio, eseguirai il deployment della funzione Cloud Run eseguendo il seguente comando.

Nota: il numero massimo di istanze deve essere impostato su un numero inferiore o uguale alla quota di GPU.

gcloud beta run deploy $FUNCTION_NAME \
  --region $REGION \
  --function gcs-cloudevent \
  --base-image nodejs22 \
  --source . \
  --no-allow-unauthenticated \
  --max-instances 2 # this should be less than or equal to your GPU quota

7. Crea il sidecar

Per scoprire di più sull'hosting di Ollama in un servizio Cloud Run, visita la pagina https://cloud.google.com/run/docs/tutorials/gpu-gemma-with-ollama

Vai alla directory del sidecar:

cd ../ollama-gemma3

Crea un file Dockerfile con i seguenti contenuti:

FROM ollama/ollama:latest

# Listen on all interfaces, port 11434
ENV OLLAMA_HOST 0.0.0.0:11434

# Store model weight files in /models
ENV OLLAMA_MODELS /models

# Reduce logging verbosity
ENV OLLAMA_DEBUG false

# Never unload model weights from the GPU
ENV OLLAMA_KEEP_ALIVE -1

# Store the model weights in the container image
ENV MODEL gemma3:4b
RUN ollama serve & sleep 5 && ollama pull $MODEL

# Start Ollama
ENTRYPOINT ["ollama", "serve"]

Crea l'immagine

gcloud builds submit \
   --tag $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/ollama-gemma3 \
   --machine-type e2-highcpu-32

8. Aggiorna la funzione con il sidecar

Per aggiungere un contenitore a un servizio, a un job o a una funzione esistenti, puoi aggiornare il file YAML in modo che contenga il contenitore.

Recupera il file YAML per la funzione Cloud Run appena di cui hai eseguito il deployment eseguendo:

gcloud run services describe $FUNCTION_NAME --format=export > add-sidecar-service.yaml

Ora aggiungi il sidecar al file CRf aggiornando il file YAML come segue:

  1. Inserisci il seguente frammento YAML direttamente sopra la riga runtimeClassName: run.googleapis.com/linux-base-image-update. -image deve essere allineato all'elemento contenitore di importazione -image
    - image: YOUR_IMAGE_SIDECAR:latest
        name
: gemma-sidecar
        env
:
       
- name: OLLAMA_FLASH_ATTENTION
          value
: '1'
        resources
:
          limits
:
            cpu
: 6000m
            nvidia
.com/gpu: '1'
            memory
: 16Gi
        volumeMounts
:
       
- name: gcs-1
          mountPath
: /root/.ollama
        startupProbe
:
          failureThreshold
: 2
          httpGet
:
            path
: /
            port: 11434
          initialDelaySeconds: 60
          periodSeconds: 60
          timeoutSeconds: 60
      nodeSelector:
        run.googleapis.com/
accelerator: nvidia-l4
      volumes
:
       
- csi:
            driver
: gcsfuse.run.googleapis.com
            volumeAttributes
:
              bucketName
: YOUR_BUCKET_GEMMA_NAME
          name
: gcs-1
  1. Esegui il seguente comando per aggiornare il frammento YAML con le variabili di ambiente:
sed -i "s|YOUR_IMAGE_SIDECAR|$IMAGE_SIDECAR|; s|YOUR_BUCKET_GEMMA_NAME|$BUCKET_GEMMA_NAME|" add-sidecar-service.yaml

Il file YAML completo dovrebbe avere il seguente aspetto:

##############################################
# DO NOT COPY - For illustration purposes only
##############################################

apiVersion
: serving.knative.dev/v1
kind
: Service
metadata
:
  annotations
:    
    run
.googleapis.com/build-base-image: us-central1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/nodejs22
    run
.googleapis.com/build-enable-automatic-updates: 'true'
    run
.googleapis.com/build-function-target: gcs-cloudevent
    run
.googleapis.com/build-id: f0122905-a556-4000-ace4-5c004a9f9ec6
    run
.googleapis.com/build-image-uri:<YOUR_IMAGE_CRF>
    run
.googleapis.com/build-name: <YOUR_BUILD_NAME>
    run
.googleapis.com/build-source-location: <YOUR_SOURCE_LOCATION>
    run
.googleapis.com/ingress: all
    run
.googleapis.com/ingress-status: all
    run
.googleapis.com/urls: '["<YOUR_CLOUD_RUN_FUNCTION_URLS"]'
  labels
:
    cloud
.googleapis.com/location: <YOUR_REGION>
  name
: <YOUR_FUNCTION_NAME>
 
namespace: '392295011265'
spec
:
 
template:
    metadata
:
      annotations
:
        autoscaling
.knative.dev/maxScale: '4'
        run
.googleapis.com/base-images: '{"":"us-central1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/nodejs22"}'
        run
.googleapis.com/client-name: gcloud
        run
.googleapis.com/client-version: 514.0.0
        run
.googleapis.com/startup-cpu-boost: 'true'
      labels
:
        client
.knative.dev/nonce: hzhhrhheyd
        run
.googleapis.com/startupProbeType: Default
    spec
:
      containerConcurrency
: 80
      containers
:
     
- image: <YOUR_FUNCTION_IMAGE>
        ports
:
       
- containerPort: 8080
          name
: http1
        resources
:
          limits
:
            cpu
: 1000m
            memory
: 512Mi
        startupProbe
:
          failureThreshold
: 1
          periodSeconds
: 240
          tcpSocket
:
            port
: 8080
          timeoutSeconds
: 240
     
- image: <YOUR_SIDECAR_IMAGE>:latest
        name
: gemma-sidecar
        env
:
       
- name: OLLAMA_FLASH_ATTENTION
          value
: '1'
        resources
:
          limits
:
            cpu
: 6000m
            nvidia
.com/gpu: '1'
            memory
: 16Gi
        volumeMounts
:
       
- name: gcs-1
          mountPath
: /root/.ollama
        startupProbe
:
          failureThreshold
: 2
          httpGet
:
            path
: /
            port: 11434
          initialDelaySeconds: 60
          periodSeconds: 60
          timeoutSeconds: 60
      nodeSelector:
        run.googleapis.com/
accelerator: nvidia-l4
      volumes
:
       
- csi:
            driver
: gcsfuse.run.googleapis.com
            volumeAttributes
:
              bucketName
: <YOUR_BUCKET_NAME>
          name
: gcs-1
      runtimeClassName
: run.googleapis.com/linux-base-image-update
      serviceAccountName
: <YOUR_SA_ADDRESS>
      timeoutSeconds
: 300
  traffic
:
 
- latestRevision: true
    percent
: 100

##############################################
# DO NOT COPY - For illustration purposes only
##############################################

Ora aggiorna la funzione con il sidecar eseguendo il seguente comando.

gcloud run services replace add-sidecar-service.yaml

Infine, crea l'attivatore Eventarc per la funzione. Questo comando lo aggiunge anche alla funzione.

Nota: se hai creato un bucket su più regioni, devi modificare il parametro --location

gcloud eventarc triggers create my-crf-summary-trigger  \
    --location=$REGION \
    --destination-run-service=$FUNCTION_NAME  \
    --destination-run-region=$REGION \
    --event-filters="type=google.cloud.storage.object.v1.finalized" \
    --event-filters="bucket=$BUCKET_DOCS_NAME" \
    --service-account=$SERVICE_ACCOUNT_ADDRESS

9. Testa la funzione

Carica un file di testo normale per il riassunto. Non sai cosa riassumere? Chiedi a Gemini una breve descrizione di 1-2 pagine sulla storia dei cani. Carica poi il file di testo nel bucket $BUCKET_DOCS_NAME per il modello Gemma3:4b per scrivere un riepilogo nei log della funzione.

Nei log viene visualizzato un messaggio simile al seguente:

---------------
Processing for objects/dogs.txt
---------------
Attempting to download: <YOUR_PROJECT_ID>-codelab-crf-sidecar-gpu-docs/dogs.txt
Sending file to Gemma 3 for summarization
...
Here's a concise summary of the document "Humanity's Best Friend":
The dog's domestication, beginning roughly 20,000-40,000 years ago, represents a unique, deeply intertwined evolutionary partnership with humans, predating the domestication of any other animal
<
...>
solidifying their long-standing role as humanity's best friend.

10. Risoluzione dei problemi

Ecco alcuni errori ortografici che potresti riscontrare:

  1. Se ricevi un errore PORT 8080 is in use, assicurati che il Dockerfile per il sidecar Ollama utilizzi la porta 11434. Inoltre, assicurati di utilizzare l'immagine sidecar corretta nel caso in cui tu abbia più immagini Ollama nel tuo repository AR. La funzione Cloud Run viene pubblicata sulla porta 8080 e, se hai utilizzato un'immagine Ollama diversa come contenitore aggiuntivo che viene pubblicata anche sulla porta 8080, riscontrerai questo errore.
  2. Se ricevi l'errore failed to build: (error ID: 7485c5b6): function.js does not exist, assicurati che i file package.json e tsconfig.json si trovino allo stesso livello della directory src.
  3. Se ricevi l'errore ERROR: (gcloud.run.services.replace) spec.template.spec.node_selector: Max instances must be set to 4 or fewer in order to set GPU requirements., nel file YAML modifica autoscaling.knative.dev/maxScale: '100' in 1 o in un valore inferiore o uguale alla tua quota GPU.