Héberger un LLM dans un sidecar pour une fonction Cloud Run

Héberger un LLM dans un sidecar pour une fonction Cloud Run

À propos de cet atelier de programmation

subjectDernière mise à jour : mars 27, 2025
account_circleRédigé par une Googleuse ou un Googleur

1. Introduction

Présentation

Dans cet atelier de programmation, vous allez apprendre à héberger un modèle gemma3:4b dans un sidecar pour une fonction Cloud Run. Lorsqu'un fichier est importé dans un bucket Cloud Storage, la fonction Cloud Run est déclenchée. La fonction envoie le contenu du fichier à Gemma 3 dans le sidecar pour la synthèse.

Points abordés

  • Effectuer une inférence à l'aide d'une fonction Cloud Run et d'un LLM hébergé dans un sidecar à l'aide de GPU
  • Utiliser la configuration de sortie VPC directe pour un GPU Cloud Run afin d'accélérer l'importation et le traitement du modèle
  • Utiliser genkit pour interagir avec votre modèle ollama hébergé

2. Avant de commencer

Pour utiliser la fonctionnalité GPU, vous devez demander une augmentation de quota pour une région compatible. Le quota requis est nvidia_l4_gpu_allocation_no_zonal_redundancy, qui se trouve dans l'API Cloud Run Admin. Voici le lien direct pour demander un quota.

3. Préparation

Définissez les variables d'environnement qui seront utilisées tout au long de cet atelier de programmation.

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

Créez le compte de service en exécutant la commande suivante:

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

Nous utiliserons ce même compte de service utilisé comme identité de la fonction Cloud Run comme compte de service pour le déclencheur Eventarc afin d'appeler la fonction Cloud Run. Si vous le souhaitez, vous pouvez créer une autre SA pour Eventarc.

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

Accordez également au compte de service l'accès pour recevoir des événements Eventarc.

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

Créez un bucket qui hébergera votre modèle affiné. Cet atelier de programmation utilise un bucket régional. Vous pouvez également utiliser un bucket multirégional.

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

Accordez ensuite à l'administrateur de service l'accès au bucket.

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

Créez maintenant un bucket régional qui stockera les documents que vous souhaitez résumer. Vous pouvez également utiliser un bucket multirégional, à condition de mettre à jour le déclencheur Eventarc en conséquence (voir la fin de cet atelier de programmation).

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

Accordez ensuite à l'administrateur de service l'accès au bucket Gemma 3.

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

et le bucket "Docs".

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

Créer un dépôt Artifact Registry pour l'image Ollama qui sera utilisée dans le sidecar

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

4. Télécharger le modèle Gemma 3

Commencez par télécharger le modèle Gemma 3 4b sur ollama. Pour ce faire, installez ollama, puis exécutez le modèle gemma3:4b en local.

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

Dans une autre fenêtre de terminal, exécutez la commande suivante pour télécharger le modèle. Si vous utilisez Cloud Shell, vous pouvez ouvrir une fenêtre de terminal supplémentaire en cliquant sur l'icône Plus dans la barre de menu supérieure droite.

ollama run gemma3:4b

Une fois ollama en cours d'exécution, n'hésitez pas à poser des questions au modèle, par exemple :

"why is the sky blue?"

Une fois la discussion avec ollama terminée, vous pouvez quitter le chat en exécutant

/bye

Ensuite, dans la première fenêtre de terminal, exécutez la commande suivante pour arrêter de diffuser ollama en local :

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

Vous trouverez ici l'emplacement où Ollama télécharge les modèles en fonction de votre système d'exploitation.

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

Si vous utilisez Cloud Workstations, vous pouvez télécharger les modèles ollama ici : /home/$USER/.ollama/models

Vérifiez que vos modèles sont hébergés ici:

ls /home/$USER/.ollama/models

Déplacez maintenant le modèle gemma3:4b vers votre bucket GCS.

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

5. Créer la fonction Cloud Run

Créez un dossier racine pour votre code source.

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

Créez un sous-dossier nommé "src". Dans le dossier, créez un fichier nommé index.ts.

mkdir src &&
touch src
/index.ts

Remplacez le fichier index.ts par le code suivant:

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

Dans le répertoire racine crf-sidecar-gpu, créez un fichier nommé package.json et contenant le contenu suivant:

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

Créez également un fichier tsconfig.json au niveau du répertoire racine avec le contenu suivant:

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

6. Déployer la fonction

À cette étape, vous allez déployer la fonction Cloud Run en exécutant la commande suivante.

Remarque: Le nombre maximal d'instances doit être inférieur ou égal à votre quota 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. Créer le sidecar

Pour en savoir plus sur l'hébergement d'Ollama dans un service Cloud Run, consultez la page https://cloud.google.com/run/docs/tutorials/gpu-gemma-with-ollama.

Accédez au répertoire de votre sidecar:

cd ../ollama-gemma3

Créez un fichier Dockerfile avec le contenu suivant :

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

Créer l'image

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

8. Mettre à jour la fonction avec le side-car

Pour ajouter un sidecar à un service, un job ou une fonction existants, vous pouvez modifier le fichier YAML pour qu'il contienne le sidecar.

Récupérez le fichier YAML de la fonction Cloud Run que vous venez de déployer en exécutant la commande suivante:

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

Ajoutez maintenant le side-car au CRf en mettant à jour le fichier YAML comme suit:

  1. insérez le fragment YAML suivant juste au-dessus de la ligne runtimeClassName: run.googleapis.com/linux-base-image-update. Le -image doit correspondre à l'élément de conteneur d'entrée -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. Exécutez la commande suivante pour mettre à jour le fragment YAML avec vos variables d'environnement:
sed -i "s|YOUR_IMAGE_SIDECAR|$IMAGE_SIDECAR|; s|YOUR_BUCKET_GEMMA_NAME|$BUCKET_GEMMA_NAME|" add-sidecar-service.yaml

Votre fichier YAML final devrait se présenter comme suit:

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

Mettez à jour la fonction avec le sidecar en exécutant la commande suivante.

gcloud run services replace add-sidecar-service.yaml

Enfin, créez le déclencheur Eventarc pour la fonction. Cette commande l'ajoute également à la fonction.

Remarque: Si vous avez créé un bucket multirégional, vous devez modifier le paramètre --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. Tester votre fonction

Importez un fichier de texte brut à résumer. Vous ne savez pas quoi résumer ? Demandez à Gemini une description rapide de 1 à 2 pages sur l'histoire des chiens. Importez ensuite ce fichier au format texte brut dans votre bucket $BUCKET_DOCS_NAME pour que le modèle Gemma3:4b écrive un résumé dans les journaux de la fonction.

Dans les journaux, vous verrez un résultat semblable à ceci:

---------------
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. Dépannage

Voici quelques erreurs de frappe que vous pouvez rencontrer:

  1. Si vous recevez une erreur PORT 8080 is in use, assurez-vous que votre fichier Dockerfile pour votre sidecar Ollama utilise le port 11434. Assurez-vous également d'utiliser la bonne image de sidecar au cas où vous auriez plusieurs images Ollama dans votre dépôt AR. La fonction Cloud Run est diffusée sur le port 8080. Si vous avez utilisé une autre image Ollama comme sidecar qui est également diffusée sur le port 8080, vous rencontrerez cette erreur.
  2. Si vous obtenez l'erreur failed to build: (error ID: 7485c5b6): function.js does not exist, assurez-vous que vos fichiers package.json et tsconfig.json se trouvent au même niveau que le répertoire src.
  3. Si l'erreur 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. s'affiche, dans votre fichier YAML, remplacez autoscaling.knative.dev/maxScale: '100' par 1 ou par une valeur inférieure ou égale à votre quota de GPU.