Jak hostować LLM w kontenerze pomocniczym dla funkcji Cloud Run

Jak hostować LLM w kontenerze pomocniczym dla funkcji Cloud Run

Informacje o tym ćwiczeniu (w Codelabs)

subjectOstatnia aktualizacja: mar 27, 2025
account_circleDokument stworzony przez pracownika Google

1. Wprowadzenie

Z tego Codelab dowiesz się, jak hostować model gemma3:4b w kontenerze pomocniczym dla funkcji Cloud Run. Gdy plik zostanie przesłany do zasobnika Cloud Storage, uruchomi funkcję Cloud Run. Funkcja wyśle zawartość pliku do Gemma 3 w ramach podsumowania.

  • Jak wykonywać wnioskowanie za pomocą funkcji Cloud Run i LLM hostowanego w kontenerze pomocniczym z wykorzystaniem GPU
  • Jak użyć konfiguracji bezpośredniego wyjścia sieci VPC w usłudze Cloud Run GPU, aby przyspieszyć przesyłanie i udostępnianie modelu
  • Jak używać genkit do interfejsu z hostowanym modelem ollama

2. Zanim zaczniesz

Aby korzystać z funkcji GPU, musisz poprosić o zwiększenie limitu w obsługiwanym regionie. Potrzebna jest kwota nvidia_l4_gpu_allocation_no_zonal_redundancy, która znajduje się w Cloud Run Admin API. Oto bezpośredni link do strony z prośbą o przydzielenie limitu.

3. Konfiguracja i wymagania

Ustaw zmienne środowiskowe, których będziesz używać w tym ćwiczeniu.

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

Utwórz konto usługi, uruchamiając to polecenie:

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

Użyjemy tego samego konta usługi, które jest używane jako tożsamość funkcji Cloud Run, jako konta usługi dla aktywatora eventarc, aby wywołać funkcję Cloud Run. Jeśli chcesz, możesz utworzyć inną umowę partnerską dla Eventarc.

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

Przyznaj też kontu usługi dostęp do zdarzeń Eventarc.

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

Utwórz zasobnik, który będzie hostować Twój model dostrojony. Ten warsztat programistyczny korzysta z zasobnika regionalnego. Możesz też użyć zasobnika z wieloma regionami.

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

Następnie przyznaj dostęp do zasobnika użytkownikowi SA.

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

Teraz utwórz regionalny zasobnik, w którym będą przechowywane dokumenty, które chcesz podsumować. Możesz też użyć puli wieloregionalnej, pod warunkiem że zaktualizujesz odpowiednio odpowiednik Eventarc (jak pokazano na końcu tego Codelab).

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

Następnie przyznaj dostęp do zasobnika Gemma 3.

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

i zasobnik Dokumentów.

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

Utwórz repozytorium Artifact Registry dla obrazu Ollama, który będzie używany w kontenerze pobocznym.

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

4. Pobieranie modelu Gemma 3

Najpierw pobierz model Gemma 3 4b z witryny ollama. Aby to zrobić, zainstaluj narzędzie ollama, a następnie uruchom model gemma3:4b lokalnie.

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

W osobnym oknie terminala uruchom to polecenie, aby pobrać model. Jeśli używasz Cloud Shell, możesz otworzyć dodatkowe okno terminala, klikając ikonę plusa na pasku menu w prawym górnym rogu.

ollama run gemma3:4b

Gdy już uruchomisz narzędzie, możesz zadać modelowi kilka pytań, np.

"why is the sky blue?"

Gdy skończysz czatowanie z ollama, możesz zamknąć czat, uruchamiając

/bye

Następnie w pierwszym oknie terminala uruchom to polecenie, aby zatrzymać lokalną obsługę ollama

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

Tutaj możesz sprawdzić, skąd Ollama pobiera modele w zależności od systemu operacyjnego.

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

Jeśli używasz Cloud Workstations, możesz pobrać modele ollama tutaj: /home/$USER/.ollama/models

Sprawdź, czy Twoje modele są hostowane tutaj:

ls /home/$USER/.ollama/models

Teraz przenieś model gemma3:4b do zasobnika GCS

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

5. Tworzenie funkcji Cloud Run

Utwórz folder główny na kod źródłowy.

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

Utwórz podfolder o nazwie src. W folderze utwórz plik o nazwie index.ts.

mkdir src &&
touch src
/index.ts

Zaktualizuj plik index.ts za pomocą tego kodu:

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

Teraz w katalogu głównym crf-sidecar-gpu utwórz plik o nazwie package.json z tą zawartością:

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

Utwórz plik tsconfig.json na poziomie katalogu głównego z taką zawartością:

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

6. Wdrażanie funkcji

W tym kroku wdrożysz funkcję Cloud Run, uruchamiając to polecenie.

Uwaga: maksymalna liczba instancji powinna być równa lub mniejsza od limitu 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. Tworzenie kontenera

Więcej informacji o hostowaniu Ollama w usłudze Cloud Run znajdziesz na stronie https://cloud.google.com/run/docs/tutorials/gpu-gemma-with-ollama.

Przejdź do katalogu sidecar:

cd ../ollama-gemma3

Utwórz plik Dockerfile z tą zawartością:

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

Tworzenie obrazu

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

8. Zaktualizuj funkcję za pomocą sidecara

Aby dodać plik pomocniczy do istniejącej usługi, zadania lub funkcji, możesz zaktualizować plik YAML, aby zawierał plik pomocniczy.

Aby pobrać plik YAML funkcji Cloud Run, którą właśnie wdrożyłeś, wykonaj te czynności:

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

Teraz dodaj do formularza CRf dokument pomocniczy, aktualizując plik YAML w ten sposób:

  1. wstaw bezpośrednio nad wierszem runtimeClassName: run.googleapis.com/linux-base-image-update podany niżej fragment kodu YAML. Element -image powinien być zgodny z elementem kontenera napływowego -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. Aby zaktualizować fragment YAML za pomocą zmiennych środowiskowych, uruchom to polecenie:
sed -i "s|YOUR_IMAGE_SIDECAR|$IMAGE_SIDECAR|; s|YOUR_BUCKET_GEMMA_NAME|$BUCKET_GEMMA_NAME|" add-sidecar-service.yaml

Gotowy plik YAML powinien wyglądać mniej więcej tak:

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

Teraz zaktualizuj funkcję za pomocą sidecara, wykonując to polecenie.

gcloud run services replace add-sidecar-service.yaml

Na koniec utwórz dla funkcji odpowiedni aktywator Eventarc. To polecenie również dodaje je do funkcji.

Uwaga: jeśli utworzysz zasób wieloregionalny, musisz zmienić parametr --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. Testowanie funkcji

Prześlij plik tekstowy do podsumowania. Nie wiesz, co podsumować? Poproś Gemini o krótki opis 1–2-stronicowego tekstu o historii psów. Następnie prześlij ten plik tekstowy do zasobnika $BUCKET_DOCS_NAME dla modelu Gemma3:4b, aby zapisać podsumowanie w logach funkcji.

W dziennikach zobaczysz komunikat podobny do tego:

---------------
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. Rozwiązywanie problemów

Oto niektóre błędy związane z błędami w pisaniu, które możesz napotkać:

  1. Jeśli pojawi się błąd PORT 8080 is in use, sprawdź, czy plik Dockerfile dla podprogramu Ollama używa portu 11434. Sprawdź też, czy używasz prawidłowego obrazu sidecar, jeśli masz w repozytorium AR wiele obrazów Ollama. Funkcja Cloud Run działa na porcie 8080, a jeśli jako kontener pomocniczy użyjesz innego obrazu Ollama, który również działa na porcie 8080, wystąpi błąd.
  2. Jeśli pojawi się błąd failed to build: (error ID: 7485c5b6): function.js does not exist, sprawdź, czy pliki package.json i tsconfig.json znajdują się na tym samym poziomie co katalog src.
  3. Jeśli wystąpi błąd 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., w pliku YAML zmień wartość autoscaling.knative.dev/maxScale: '100' na 1 lub na wartość mniejszą lub równą kwocie dostępnej kwoty na GPU.