Como hospedar um LLM em um sidecar para uma função do Cloud Run

Como hospedar um LLM em um sidecar para uma função do Cloud Run

Sobre este codelab

subjectÚltimo mar. 27, 2025 atualizado
account_circleEscrito por um Googler

1. Introdução

Visão geral

Neste codelab, você vai aprender a hospedar um modelo gemma3:4b em um sidecar para uma função do Cloud Run. Quando um arquivo é enviado para um bucket do Cloud Storage, a função do Cloud Run é acionada. A função vai enviar o conteúdo do arquivo para Gemma 3 no sidecar para resumo.

O que você vai aprender

  • Como fazer inferência usando uma função do Cloud Run e um LLM hospedado em um sidecar usando GPUs
  • Como usar a configuração de saída VPC direta para uma GPU do Cloud Run para fazer o upload e a veiculação do modelo mais rapidamente
  • Como usar o Genkit para interagir com seu modelo de ollama hospedado

2. Antes de começar

Para usar o recurso de GPUs, você precisa solicitar um aumento de cota para uma região com suporte. A cota necessária é nvidia_l4_gpu_allocation_no_zonal_redundancy, que está na API Cloud Run Admin. Este é o link direto para solicitar a cota.

3. Configuração e requisitos

Defina as variáveis de ambiente que serão usadas neste 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

Crie a conta de serviço executando este comando:

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

Vamos usar essa mesma conta de serviço como a identidade da função do Cloud Run como a conta de serviço do acionador do eventarc para invocar a função do Cloud Run. Se preferir, crie um SA diferente para o Eventarc.

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

Conceda à conta de serviço acesso para receber eventos do Eventarc.

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

Crie um bucket que hospedará seu modelo ajustado. Este codelab usa um bucket regional. Você também pode usar um bucket multirregional.

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

Em seguida, conceda ao SA acesso ao bucket.

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

Agora, crie um bucket regional que vai armazenar os documentos que você quer resumir. Você também pode usar um bucket multirregional, desde que atualize o acionador do Eventarc de acordo (mostrado no final deste codelab).

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

Em seguida, conceda ao SA acesso ao bucket Gemma 3.

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

e o bucket de documentos.

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

Crie um repositório do Artifact Registry para a imagem do Ollama que será usada no sidecar

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

4. Fazer o download do modelo Gemma 3

Primeiro, faça o download do modelo Gemma 3 4b no ollama. Para fazer isso, instale o ollama e execute o modelo gemma3:4b localmente.

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

Agora, em uma janela de terminal separada, execute o comando a seguir para baixar o modelo. Se você estiver usando o Cloud Shell, poderá abrir outra janela de terminal clicando no ícone de adição na barra de menus no canto superior direito.

ollama run gemma3:4b

Quando o ollama estiver em execução, faça algumas perguntas ao modelo, por exemplo:

"why is the sky blue?"

Quando terminar de conversar com o ollama, saia do chat executando

/bye

Em seguida, na primeira janela do terminal, execute o comando a seguir para interromper a veiculação da ollama localmente.

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

Confira aqui onde o Ollama faz o download dos modelos, dependendo do seu sistema operacional.

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

Se você estiver usando as estações de trabalho do Cloud, confira os modelos do Ollamarain que foram transferidos por download aqui /home/$USER/.ollama/models

Confirme se os modelos estão hospedados aqui:

ls /home/$USER/.ollama/models

Agora, mova o modelo gemma3:4b para o bucket do GCS

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

5. Criar a função do Cloud Run

Crie uma pasta raiz para o código-fonte.

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

Crie uma subpasta chamada src. Dentro da pasta, crie um arquivo chamado index.ts.

mkdir src &&
touch src
/index.ts

Atualize index.ts com o seguinte código:

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

Agora, no diretório raiz crf-sidecar-gpu, crie um arquivo chamado package.json com o seguinte conteúdo:

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

Crie um tsconfig.json também no nível do diretório raiz com o seguinte conteúdo:

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

6. Implante a função

Nesta etapa, você vai implantar a função do Cloud Run executando o comando a seguir.

Observação: o número máximo de instâncias precisa ser definido como um número menor ou igual à cota de 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. Criar o sidecar

Saiba mais sobre como hospedar o Ollama em um serviço do Cloud Run em https://cloud.google.com/run/docs/tutorials/gpu-gemma-with-ollama

Acesse o diretório do sidecar:

cd ../ollama-gemma3

Crie um arquivo Dockerfile com o seguinte conteúdo:

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"]

Criar a imagem

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

8. Atualizar a função com o sidecar

Para adicionar um sidecar a um serviço, job ou função, atualize o arquivo YAML para incluir o sidecar.

Extraia o YAML da função do Cloud Run que você acabou de implantar executando:

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

Agora adicione o sidecar ao CRf atualizando o YAML da seguinte maneira:

  1. Insira o seguinte fragmento YAML diretamente acima da linha runtimeClassName: run.googleapis.com/linux-base-image-update. O -image precisa estar alinhado com o item do contêiner de entrada -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. Execute o comando abaixo para atualizar o fragmento YAML com suas variáveis de ambiente:
sed -i "s|YOUR_IMAGE_SIDECAR|$IMAGE_SIDECAR|; s|YOUR_BUCKET_GEMMA_NAME|$BUCKET_GEMMA_NAME|" add-sidecar-service.yaml

O arquivo YAML concluído vai ficar assim:

##############################################
# 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
##############################################

Agora atualize a função com o sidecar executando o comando a seguir.

gcloud run services replace add-sidecar-service.yaml

Por fim, crie o gatilho do Eventarc para a função. Esse comando também adiciona a função.

Observação: se você criou um bucket multirregional, mude o parâmetro --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. Testar a função

Faça upload de um arquivo de texto simples para a geração de resumo. Não sabe o que resumir? Peça ao Gemini uma descrição rápida de uma a duas páginas sobre a história dos cães. Em seguida, faça upload desse arquivo de texto simples para o bucket $BUCKET_DOCS_NAME do modelo Gemma3:4b para gravar um resumo nos registros de função.

Nos registros, você vai encontrar algo semelhante ao seguinte:

---------------
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. Solução de problemas

Confira alguns erros de digitação que você pode encontrar:

  1. Se você receber um erro PORT 8080 is in use, verifique se o Dockerfile do sidecar do Ollama está usando a porta 11434. Além disso, verifique se você está usando a imagem de sidecar correta caso tenha várias imagens do Ollama no repositório de RA. A função do Cloud Run é veiculada na porta 8080. Se você usou uma imagem diferente do Ollama como o sidecar que também é veiculado na 8080, vai encontrar esse erro.
  2. Se você receber o erro failed to build: (error ID: 7485c5b6): function.js does not exist, verifique se os arquivos package.json e tsconfig.json estão no mesmo nível do diretório src.
  3. Se você receber o erro 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., no arquivo YAML, mude autoscaling.knative.dev/maxScale: '100' para 1 ou para algo menor ou igual à cota de GPU.